深入浅出Node.js学习笔记(八)

构建Web应用

前后端采用的语言都是JavaScript,在跨越HTTP进行沟通时的好处;

  • 无须切换语言环境,部分知识不会因为语言环境的切换而丢失,会有一些额外的好处;
  • 数据(因为JSON)可以很好的实现跨前后端直接使用;
  • 一些业务(如模板渲染)可以很自由地轻量地选择前端还是后端进行,因为编程语言相同,所以切换代价小;

1. 基础功能

对于一个Web应用而言,在具体的业务上,可能存在的需求有:

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

1.1 请求方法

请求方法包括:

  1. GET;
  2. POST;
  3. HEAD;
  4. DELETE;
  5. PUT;
  6. CONNECT;

1.2 路径解析

客户端代理(浏览器)会将完整的URL地址解析成报文,将路径和查询部分放在报文第一行。

最常见的根据路径进行业务处理的应用是静态文件服务器,会根据路径去查找磁盘中的文件,然后将其响应给客户端。

还有一种常见的分发场景是根据路径来选择控制器,预设路径为控制器和行为组合,无须额外配置路由信息。

1.3 查询字符串

字符串会跟随在路径后,形成请求报文首行的第二部分。

在业务调用产生之前,中间件或者框架会将查询字符串转换,然后挂载在对象上供业务使用。

1.4 Cookie

  1. 初识Cookie

    Cookie的处理分为如下几步:

    • 服务器向客户端发送Cookie;
    • 浏览器将Cookie保存;
    • 之后每次浏览器都会将Cookie发向服务器端;
可选参数会影响浏览器在后续发送给服务器端的行为。主要为以下几个选项:

*   path;
    
*   Expires和Max-Age
    
*   HttpOnly
    
*   Secure
    
  1. Cookie的性能影响

    针对Cookie设置过多,造成的带宽的浪费的性能优化方案:

    • 减少Cookie的大小

      如果在域名的根节点设置Cookie,几乎所有子路径下的请求都会带上这些Cookie。

    • 为静态组件使用不同的域名

      为不需要Cookie的组件换个域名可以实现减少无效Cookie的传输,还可以突破浏览器下载线程的限制,但是将会增加域名转换为IP的DNS查询。

    • 减少DNS查询

      减少DNS查询和使用不同的域名看似冲突,但使用DNS缓存会削弱这个副作用。

1.5 Session

通过Cookie,浏览器和服务器可以实现状态的记录。但Cookie并非完美,缺点是:体积过大;Cookie在前后端均可以被修改,数据极易被篡改和伪造。综上,Cookie对于敏感数据的保护是无效的。

为了解决Cookie敏感数据的问题,Session应运而生。

Session的数据只保留在服务端,客户端无法修改,数据的安全有一定的保障,数据也无需在协议中每次传输。

如何将每个客户的服务器的数据一一对应:

  1. 基于Cookie来实现用户和数据的映射;

    虽然将所有的数据都放在Cookie中不可取,但是将口令放在Cookie中是可以的。口令一旦被篡改,就丢失了映射关系,也无法修改服务器端存在的数据了。

    Session的有效期通常较短,普遍的设置是20分钟,如果20分钟之内服务器端和客户端没有交互产生,服务器端将数据删除。由于数据过期时间较短,且在服务器端存储数据,因此安全性相对较高。

    口令是如何产生的?

    一旦服务器端启用了Session,它将约定一个键值作为Session的口令,这个值可以随意约定。一旦服务器端检查到用户请求Cookie没有携带该值,它就会为之生成一个值,这个值是唯一且不重复的值,并设定超时时间。

    这种方案依赖Cookie的实现。

    如果客户端禁止使用Cookie,这个世界大多数的网站将无法实现登录等操作。

  2. 通过查询字符串来实现浏览器端和服务器端数据的对应;

    它的原理是检查请求的查询字符串,如果没有值,会先生成新的带值的URL。

  3. 利用HTTP请求头中的ETag;
  • Session与内存

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

    采用第三方缓存来存储Session会引起的一个问题时引起网络访问。

    理论上来说,访问网络中的数据比访问本地磁盘中的数据要慢,涉及到握手、传输、网络终端自身的磁盘I/O等。

    采用第三方高速缓存的理由:

    • Node与缓存服务保持长连接,而非频繁的短连接,握手导致的延迟只影响初始化;
    • 高速缓存直接在内存中进行数据存储和访问;
    • 缓存服务通常与Node进程会比在相同的机器上或者相同的机房里,网络速度受到的影响较小;
  • Session与安全

    Session的安全主要指如何让这个口令更加安全。

    一种做法是将这个口令通过私钥加密进行签名,使得伪造的成本较高。

    一种方案是将客户端的某些独有信息与口令作为原值,然后签名,这样攻击者一旦不在原始的客户端进行访问,就会导致签名失败。这些独有信息包括用户IP和用户代理(User Agent)。

  • XSS漏洞

    XSS的全称是跨站脚本攻击(Cross Site Scripting),通常都是网站开发者决定哪些脚本可以执行在浏览器端。

    XSS主要形成的原因多数是用户的输入没有被转义,而被直接执行。

1.6 缓存

在HTTP之上构建的应用,其客户端除了比普通桌面应用具备更轻量的升级和部署等特性外,在跨平台、跨浏览器、跨设备上也具有独特的优势。

传统客户端在安装后的应用过程中仅仅需要传输数据,Web应用还需要传输构成界面的组件(HTML、CSS、JavaScript)。

为了提高性能,关于缓存的规则:

  • 添加Expires或Cache-Control到报文头中;
  • 配置ETags;
  • 让Ajax可缓存;

通常来说,POST、DELETE、PUT这类带行为性的请求操作一般不做任何缓存,大多数缓存只应用在GET请求中。

简单来说,本地没有文件时,浏览器会请求服务端的内容,并将这部分内容放置在本地的某个缓存目录中。第二次请求时,它将对本地文件进行检查,如果不能确定这份本地文件是否可以直接使用,它将发起一次请求。

所谓条件请求,就是在普通的GET请求报文中,附带If-Modified-Since字段。

它将询问服务器是否有更新的版本,本地文件的最后修改时间。如果服务器没有新的版本,只需响应一个304状态码,客户端就使用本地版本。如果服务器端有新的版本,就将新的内容发送给客户端,客户端放弃本地版本。

条件请求采用时间戳的方式实现的缺陷:

  • 文件的时间戳改定但内容并不一定改定;
  • 时间戳只能精确到秒级别,更新频繁的内容将无法生效;

HTTP1.1中引入ETag解决采用时间戳的缺陷。

ETag的全称是Entity Tag,由服务器端生成,服务器端可以决定它的生成规则。如果根据文件内容生成散列值,那么条件请求将不会受到时间戳改动造成的带宽浪费。

尽管条件请求可以在文件内容没有修改的情况下节省带宽,但是依然会发起一个HTTP请求,使得客户端依然会花一定时间来等待响应。最好的方案就是连条件请求都不用发起,在服务器端响应内容时,让浏览器明确地将内存缓存起来。在响应里设置Expires或Cache-Control头,浏览器将根据改值进行缓存。

Expires是一个GMT格式的时间字符串。

浏览器在接到这个过期值后,只有本地还存在这个缓存文件,在到期时间之前我它都不会发起请求。但是Expires的缺席在于浏览器和服务器之间的时间可能不一致,可能导致文件提前过期,或者到期后文件并没有被删除。

Cache-Control能够避免浏览器端与服务器端时间不同步带来的不一致问题,只要进行类似倒计时方式计算过期时间即可。Cache-Control的值还能设置public、private、no-cache、no-store等能够更精细地控制缓存的选项。

在浏览器中,Expires和Cache-Control同时存在的话,且被同时支持时,max-age会覆盖Expires。

  • 清除缓存

    设置缓存可以达到节省带宽的目的,但是缓存一旦设定,当服务器端意外更新内容时,却无法通知客户端更新。这使得在使用缓存时,也要为其设定版本号,所幸浏览器是根据URL进行缓存。

    一般的更新机制有:

1.7 Basic认证

Basic认证是当客户端与服务器端进行请求时,允许通过用户名和密码实现的一种身份认证方式。

Basic认证有太多的缺点,虽然经过Base64加密后在网络中传输,但是这近乎明文,十分的危险,一般只有在HTTPS的情况才会使用。

2. 数据上传

在业务中,需要接受的一些数据包括:表单提交、文件提交、JSON上传、XML上传等。

2.1 表单数据

最常见的数据提交就是通过网页表单提交数据到服务器端。

2.2 其他格式

除了表单数据外,常见的提交还有JSON和XML文件等,都是依据Content-Type中的值决定,其中JSON类型的值为appliction/json,XML的值为application/xml。

2.3 附件上传

除了常见的表单和特殊格式的内容提交,还有一种比较独特的表单,特殊表单与普通表单的差异在于该表单中可以含有file类型的控件,以及需要指定表单属性enctype为multipart/form-data。

2.4 数据上传与安全

内存和CSRF相关的安全问题。

  1. 内存限制

    在解析表单、JSON和XML部分,采用的策略是先保存用户提交的所有数据,然后在解析处理,最后才传递给业务逻辑。

    这种策略存在潜在的问题时,它仅仅适合数据量小的提交请求,一旦数据量过大,将发生内存被占光的情况。

    解决此问题的方案:

    • 限制上传内容的大小,一旦超过限制,停止接受数据,并响应400状态码;
    • 通过流式解析,将数据流导向磁盘中,Node只保留文件路径等小数据;
  2. CSRF

    CSRF的全称是Cross-SIte Request Forgery(跨站请求伪造)。

3. 路由解析

3.1文件路径

你可能感兴趣的