AngularJS权威指南-XHR实战

《AngularJS 权威教程》读书笔记,第十六章——XHR实践。

跨域

同源策略允许页面从同一个站点加载和执行特定的脚本。
浏览器全面禁止了页面加载或执行与自身来源不同的域的任何脚本。

感觉这里说的两句话是大错特错的,浏览器只是对 XHR 异步请求有同源策略的限制吧!iframe, script, img,这些标签还不是可以虽然加载域外资源,jsonp 不也是利用了这一点嘛!难道是翻译这本书的人水平太渣。。。
实现跨域请求的三种方式:

  1. JSONP
  2. CORS
  3. 服务器代理

JSONP

JSONP,名字来源:P 代表“填充”或“前缀”。假如在服务器上启用了 CORS(从这里可以看出 CORS 应该是:扩展了 HTTP 请求头实现的,也就说了改进了 HTTP 协议,而新的浏览器才会跟进这些新的改变),在新的浏览器下,跨域文档也可以通过 XMLHttpRequest 享受该服务。在不支持 CORS 的旧浏览器,跨域文档只能通过<script>元素访问服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function getJSONP(url, callback){
var cbnum = 'cb' + getJSONP.counter++; // 每次自增计数器
var cbname = 'getJSONP.' + cbnum; // 作为 JSONP 函数的属性
// 将回调函数名称以表单编码的形式添加到 URL 的查询部分中
if(url.indexOf('?') === -1){
url += '?jsonp=' + cbname;
}else{
url += '&jsonp=' + cbname;
}
var script = document.createElement('script');
getJSONP[cbnum] = function(response){
try{
callback(response);
}
finally{
delete getJSONP[cbnum];
script.parentNode.removeChild(script);
}
};
script.src = url;
document.body.appendChild(script);
}
getJSONP.counter = 0;

至于 JSONP 请求的的响应状态码,响应头信息怎么来呢?使用<script>标签就是相当于利用浏览器自身的网络请求功能请求数据,而我们的 js 是读取不到这种网络请求的响应信息的,所以一般服务器返回 JSONP 响应时会包含一个meta 对象来表示响应头信息。无图无真相:
JSONP 响应

CORS

近年来,W3C 指定了跨域资源共享来通过标准的方式取代 JSONP。
CORS 规范简单地扩展了标准的 XHR 对象,以允许 JavaScript 发送跨域的 XHR 请求。它会通过遇检查(preflight)来确认是否有权限向目标服务器发送请求。
W3C 指定 CORS 规范时对很多细节进行了抽象,并使其对客户端开发者透明,让开发者可以像发送同域请求一样方便地发送跨域请求。

AngularJS客户端设置

为了在 AngularJS 中使用 CORS,首先需要告诉 AngularJS 我们正在使用 CORS,使用 config 方法在应用模块上设置两个参数以达到此目的。
首先,告诉 AngularJS 使用 XDomain,并从所有的请求中把 X-Request-With 头移出掉(X-Request-With 头默认就是移出掉的,但是再次确认没有坏处),为啥这么操作就是发送 CORS 请求了呢?我也不知道,应该是 AngularJS 的一种约定吧!

1
2
3
4
5
angular.module('myApp', [])
.config(function($httpProvider){
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
});

服务器端CORS支持

后端服务器必须支持 HTTP 协议的 OPTIONS 方法。
后端服务器响应中 CORS 相关的响应头:

  1. Access-Control-Allow-Origin:* 表示会接收从任何来源发来的请求
  2. Acceess-Control-Allow-Credentials:默认情况下 CORS 请求不会发送 cookie。如果服务器返回了这个头,那么就可以通过 withCredentials 设置为 true 来将 cookie 同请求一起发送出去(如果将 $http 发送的请求中的 withCredentials 设置为 true,但服务器没有返回 Access-Control-Allow-Credentials,请求就会失败,反之亦然)。

CORS 请求分为简单和非简单两种类型。

简单请求

啥是简单请求:

  1. HTTP 请求方法是这三种的某一种:HEAD、GET、POST
  2. HTTP 请求头只有以下的一种或几种:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(application/x-www-form-urlencoded, multipart/form-data, text/plain)

对于简单请求,浏览器可以不需要使用 CORS 就发送这类请求。简单请求不要求浏览器和服务器之间有任何的特殊通信。

非简单请求

啥事非简单请求呢?不是简单请求的都是非简单请求。
对于非简单请求,尽管在客户端开发者看来没有什么不同,但浏览器会以不同的方式处理它们。浏览器实际上会发送两个请求:预请求和请求。浏览器首先向服务器发送预请求来获得发送请求的许可,只有许可通过了,浏览器才会发送真正的请求。浏览器处理 CORS 的过程是透明的,同简单请求一样,浏览器会给预请求和请求都加上 Origin 头。
预请求过程中的请求:

  1. 预请求的请求类型是 OPTIONS 类型
  2. Access-Control-Request-Method:这个头是请求所使用的 HTTP 方法,会始终包含在请求中
  3. Access-Control-Request-Headers:可选,这个头的值是一个以逗号分隔的非简单头列表,列表中的每个头都会包含在这个请求中

预请求过程中的服务器端处理:
服务器端必须可以接受这个请求,然后检查 HTTP 方法和头的合法性。如果通过了检查,服务器会在响应中添加下面这个头:

  1. Access-Control-Allow-Origin:值是请求的来源或者星号
  2. Access-Control-Allow-Methods:这是服务器端可以接受的 HTTP 方法列表,浏览器可以缓存这个列表,以后非简单的 CORS 请求可以不必再次发送预请求
  3. Access-Control-Allow-Headers:如果设置了 Access-Control-Request-Headers 头,服务器必须在响应中添加同一个头

如果预请求返回了 200 状态码,真正的请求就会发出了。

服务器端代理

最后一个简单粗暴的实现跨域请求的方案就是架设服务器端代理,浏览器客户端的请求有同源策略的限制,但是后台程序的请求却没有任何限制的,所以可以架设一台代理服务器来负责向第三方发送实际的请求。

AJAX请求中使用XML

涨姿势了,异步请求中还可以使用XML格式响应数据,不过前端得需要添加一个可以将XML转换为JSON格式的类库X2JS,不过应该没人闲的蛋疼这么干了吧!