构建Web应用

从http模块中服务器端的request事件开始分析。request事件发生于网络连接的建立,客户端向服务器端发送报文,服务器端解析报文,发送HTTP请求的报头时.

在具体的业务中,一般会有如下需求:

  • 请求方法的判断
  • URL的路径解析
  • URL中查询字符串解析
  • Cookie的解析
  • Basic认证
  • 表单数据的解析
  • 任意格式文件的上传处理

Cookie的处理分为如下几步:

  • 服务器向客户端发送Cookie
  • 浏览器将Cookie保存
  • 之后每次刷新浏览器都会将Cookie发向服务器端

Cookie参数

  • path表示这个Cookie影响到的路径,当前访问的路径不满足该匹配时,浏览器不发送这个Cookie
  • ExpiresMax-Age是用来告知浏览器这个Cookie何时过期的,如果不设置该选项,在关闭浏览器时会丢失掉这个Cookie。如果设置了过期时间,浏览器将会把Cookie内容写入到磁盘中保存,下次打开浏览器依然有效。Expires的值是一个UTC格式的时间字符串,告知浏览器此Cookie何时过期。Max-Age则告知浏览器多久后过期,当两个值同时存在时,且同时被支持时,Max-Age会覆盖Expires
  • HttpOnly告知浏览器不允许通过脚本document.cookie去更改这个Cookie值,事实上,设置HttpOnly之后,这个值在document.cookie中不可见,但是在HTTP请求的过程中,依然会发送这个Cookie到服务器端
  • Secure 当Secure值为true时,在HTTP中是无效的,在HTTPS中才有效,很难被窃听到

Cookie的性能影响

  • 减少Cookie的大小
  • 为静态组件使用不同的域名
  • 减少DNS查询

Session

Session与内存

为了解决性能问题和Session数据无法跨进程共享的问题,常用的方案是将Session集中化,将原本可能分散在多个进程里的数据,统一转移到集中的数据存储中。目前常用的工具是rediesmemcached等。通过这些高效的缓存,node进程无须在内部维护数据对象,垃圾回收问题和内存限制问题都迎刃而解


Session与安全

典型的XXS(跨站脚本攻击 Cross Site Scripting)拿到用户口令,实现伪造,通常都是由网站开发者决定哪些脚本可以执行在浏览器端,不过XXS漏洞会让别的脚本执行。它的主要形成原因多数是用户的输入没有被转义,而被直接执行。


缓存

使用缓存流程图

使用缓存流程图

缓存设置

清除缓存

知晓了如何设置缓存,以达到节省网络带宽的目的,但是缓存一旦设定,当服务器端意外更新内容时,却无法通知客户端更新。这使得我们在使用缓存时也要为其设定版本号,所幸浏览器根据URL进行缓存,那么一旦内容更新时,我们就让浏览器发起新的url请求,使新内容能够被客户端更新。一般的更新机制有如下两种:

  • 每次发布,路径中跟随web应用的版本号: http://url.com/?v=20170801
  • 每次发布,路径中跟随该文件内容的hash值: http://url.com/?hash=afadsad11sad

一般来说根据文件内容的hash值进行缓存淘汰更加高效,因为文件内容不一定跟随web应用的版本而更新,而内容没有更新时,版本号的改动导致更新毫无意义,因此以文件内容形成的hash值更精确


数据上传

Node的http模块只对HTTP报文的头部进行了解析,然后触发request事件。如果请求中还带有内部部分(如POST请求,它具有报头和内容),内容部分需要用户自行接收和解析。通过设置报头的Transfer-EncodingContent-Length即可判断请求中是否带有内容。

表单数据

默认的表单提交,请求头中的Content-Type字段值为application/x-www-form-urlencoded

其他格式

常见的提交还有JSON和XML文件等,判断和解析他们的原理都比较相似,都是依据Content-Type的值决定,其中JSON类型的值为application/json,XML的值为application/xml

需要注意的是,在Content-Type中可能还会附带charset=utf-8的编码信息。


中间件与性能

中间件的工作模型

中间件的工作模型

主要能提升性能的点:

  • 编写高效的中间件 提升单个中间件的处理速度,以尽早调用next()执行后续逻辑。需要知道的是,一旦中间件被匹配,那么每个请求都会使该中间件执行一次,哪怕它只浪费了1毫秒的执行时间都会让我们QPS显著下降,常见的优化方法有几种。

    • 使用高效的方法,必要时通过jsperf.com测试基准性能
    • 缓存需要重复计算的接口
  • 合理利用路由,避免不必要的中间件执行


页面渲染

MIME

MIME的全称是Multipurpose Internet Mail Extensions,从名字可以看出,它最早用于电子邮件,后来也应用到浏览器中。不同的文件类型具有不同的MIME值,如JSON文件的值为application/json,XML文件的值为application/xml,PDF文件的值为application/pdf

Node社区中mime模块可以判断文件类型。

var mime = require('mime')

mime.lookup('/path/to/file.txt') //=> 'text/plain'
mime.lookup('file.txt') //=> 'text/plain'
mime.lookup('.TXT') //=> 'text/plain'
mime.lookup('htm') //=> 'text/html'

Content-Disposition: 无论响应的内容是什么样的MIME值,不要求客户端去打开它,只需弹出并下载它。