在做webapp的项目 常用的技术架构是 angularjs+api , 在调用api 的时候一般都是跨域请求
手机端的高级浏览器对跨域的支持很好,但是在跨域的情况下还有需求意想不到的情况
发送请求的时候 设置不同的响应头会导致浏览器不同的情况,发送一些头会导致浏览器发送一个复杂请求
复杂请求中 会先发送一个 OPTIONS 的请求,这个请求并不是开发者代码中的主动发出 而是浏览器发送的

移动3G 的网络情况下 , OPTIONS 的响应数度很慢 虽然不会超时(60s)
OPTIONS 是一个 预请求 , 目的是先请求下这个接口是否可请求 有权限读取,这是一个好的设计
但是 从另一面来讲,多发送一个请求 性能上有损失,移动3G在对OPTIONS请求的传递响应巨慢

js原生发送一个http请求

ajax对象

1
var xhr = new XMLHttpRequest();

开启连接口

1
2
3
4
xhr.open(method, url, async)
method 请求方法
url 请求地址
async 是否异步

设置请求头

1
xhr.setRequestHeader(key, val)

发送

1
2
xhr.send(data)
data 如果是POST请求 data 就是body体的内容 其他情况为 null 就ok 了

设置响应

1
2
3
4
5
6
7
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) { // http 响应头的code
//
}
}
}

一个原生ajax源码封装

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
window.ajax = (function () {
/**
* 实例化并返回ajax对象
* @return {[type]} [description]
*/
var getXHR = function () {
var xhr = null;
if (typeof XMLHttpRequest != "undefined") {
xhr = new XMLHttpRequest();
}
else if (typeof ActiveObject != "undefined") {
var versions = [
"MSXML2.XMLHttp.6.0",
"MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp",
];
for (var i = 0; i < versions.length; i++) {
try {
xhr = new ActiveObject(versions[i]);
}
catch (e) {
//
}
}
}
else {
throw new Error('浏览器不支持ajax');
}
return xhr;
}
/**
* 设置发送请求是需要的参数
* @param {[type]} options [description]
* @return {[type]} [description]
*/
var settings = function (options) {
var opt = {};
// method
opt.method = typeof options.method == "undefined" ? "GET" : options.method.toUpperCase();
if (!isAllowMethod(opt.method)) {
opt.method = "GET";
}

// gateway
opt.url = options.url;
if (typeof opt.url == "undefined") {
throw new Error("请填写请求的地址");
}

// data
opt.data = typeof options.data == "undefined" ? null : parseParams(options.data);
if (opt.method == "GET" && opt.data != null) {
opt.url = '?' + opt.data;
}

return opt;
}
/**
* 检查是否是允许的http请求方式
* @param {[type]} method [description]
* @return {Boolean} [description]
*/
var isAllowMethod = function (method) {
var allows = ["GET", "POST", "PUT", "HEAD", "OPTIONS", "DELETE", "TRACE", "CONNECT"];
for (var i = 0; i < allows.length; i++) {
if (allows[i] == method) {
return true;
}
}
return false;
}
/**
* 处理请求是后的参数
* @param {[type]} data [description]
* @return {[type]} [description]
*/
var parseParams = function (data) {
var params = '';
for (var key in data) {
params += key + '=' + data[key] + '&';
}
return params.substring(0, params.length - 1);
}
/**
* 设置请求的头信息
* @param {[type]} xhr [description]
* @param {[type]} headers [description]
*/
var setHeaders = function (xhr, headers) {
if (typeof headers != "undefined") {
for (var key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
}
}
/**
* 对外可访问方法
*/
return {
run : function (options) {
var xhr = getXHR();
var opts = settings(options);
// 打开链接
xhr.open(opts.method, opts.url);
// 设置请求头
setHeaders(xhr, options.headers);
// 发送请求
xhr.send(opts.data);
// 设置响应成功后的处理
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
// console.log(xhr.responseText);
options.success(xhr.responseText);
}
}
}
}
}
})();

// 使用举例
ajax.run({
url : 'data.txt',
method : 'post',
data : {
"name" : "fantasy",
"age" : 12
},
headers : {
"Content-Type" : "application/json"
},
success : function (data) {
console.log(data);
}
});

什么是跨域

页面发送一个ajax请求的时候
当前发送请求方叫做 Origin 叫做域 ,当请求一个资源的时候 资源的HOST就是他的域
比如我们在 baidu.com/login.html 页面请求 baidu.com/list.php 这个资源 这里的源 就是域名 www.baidu.com
如果请求其他的域名下的资源 www.360.com/corn.php 这个时候就存在跨域问题

具体的跨域情况如下:

|发送ajax请求所在页面的域|请求的资源地址|说明|
|–|–|
|http://www.baidu.com/index.html|http://www.360.com/list.php|不同域名
|http://www.baidu.com/index.html|http://www.baidu.com:8081/list.php|同域名 不同端口
|http://www.baidu.com/index.html|https://www.baidu.com/list.php|同域名 不同协议
|http://www.baidu.com/index.html|http://115.239.210.27/list.php|域名与ip
|http://login.baidu.com/index.html|http://lib.baidu.com/list.php|不同子域名
|http://baidu.com/index.html|http://www.baidu.com/list.php|主域名与子域名

如何跨域

如下图:

对象 XMLHttpRequest 报错了
请求是从域 http://www.webcors.dev/ 发送到 域 http://www.cors1.dev ,这是一个明显的跨域问题
但是看图我们可以看出来 其实这个请求已经发出去了,服务器也是将结果返回了,Content Download 这个是内容下载时间
但是浏览器并没有将这个内容显示给我们 也就是说这是浏览器中的安全机制
如何才能跨域 ? 接着看这个错误

1
No 'Access-Control-Allow-Origin' header is present on the requested resource.

在响应中没有找到 Access-Control-Allow-Origin 这个响应头

这说明我们需要在服务端设置响应头, 浏览器通过这个 HTML5 中的相应头 来决定这个跨域请求的数据是否有权显示这个内容

其实跨域这块有几个地方是需要服务器添加响应头才能实现的,浏览器是需要通过读取响应头来决定是否显示的

Access-Control-Allow-Origin * 声名哪些源是允许跨域请求的 * 表示任何域都可以请求 你也可以指定一个允许的域名 只能指定一个不能写多个 *.baidu.com 这种的泛解析也是不行的 他是完全匹配

Access-Control-Allow-Headers Origin,Accept,... 声明哪些请求头是可以跨域请求的

Access-Control-Allow-Method GET,POST,HEAD 声明跨域请求允许的方法

Access-Control-Allow-Credentials true 跨域时候是否允许传递 cookie 当使用这个参数的时候 Access-Control-Allow-Origin 这个参数是不能设置成 *

服务端如何设置跨域

举例
apache httpd.conf

1
2
3
4
5
6
7
8
<Directory />
Options FollowSymLinks
AllowOverride All
Require all granted
Header set Access-Control-Allow-Origin *
Header set Access-Control-Allow-Method GET,POST,HEAD
Header set Access-Control-Allow-Origin Origin,Content-Type,Accept,X-Requested-With
<Directory>

nginx http.conf

1
2
3
4
5
location / {
add_header Access-Control-Allow-Origin *
add_header Access-Control-Allow-Method GET,POST,HEAD
add_header Access-Control-Allow-Origin Origin,Content-Type,Accept,X-Requested-With
}

跨域相关链接参考

复杂请求

在上面讲到的跨域请求问题,这样的背景下 有时候你会发现 你只发出去了一个请求 但是在
浏览器里面能看到发出去的却是两个 第一个是OPTIONS请求 第二个才是你要发送的

出现这个现象是浏览器的一个 简单请求 复杂请求 的一个判断,复杂请求模式下 浏览器在发送请求前先
发送一个 OPTIONS 形式的 预请求 , 后面才会发送真正的请求

浏览器是如何来区分 是 复杂请求 还是 简单请求的呢?

简单请求:

HTTP请求方式是 下列的一种

1
2
3
HEAD
GET
POST

HTTP请求头仅包含下列选项

1
2
3
4
5
6
7
8
9
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
Content-Type 的值只能是下列的一种
application/x-www-form-urlencoded
multipart/form-data
text/plain

任何不满足上面要求的都会被当做复杂请求

举例如下

1
2
3
4
5
6
7
8
9
10
11
var url = "http://www.cors1.dev";
ajax.run({
url : url,
method : 'get',
headers : {
"Content-Type" : "application/json"
},
success : function (data) {
console.log(data);
}
});

虽然是 GET 请求但是 在请求头的 Content-Type 设置的并不是规定的三个的之一
浏览器会当作复杂请求来处理

angularjs 如何设置 Content-Type

angularjs 如何发送一个ajax请求

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
28
<div ng-app="app" ng-controller="demoController">
<ul>
<li ng-repeat="x in names">
{{x.id + ' ' + x.name}}
</li>
</ul>
</div>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
<script type="text/javascript">
var url = "http://newbijia.work.dev/getBrand?type=wap";
var app = angular.module('app', []);
var data = {};

app.controller('demoController', function ($scope, $http) {
app.config(function ($httpProvider) {
$httpProvider.defaults.headers.post = {}
});
});
$http({
method : "POST",
url : url,
headers : {
"Accept" : "test",
"Content-Type" : "application/json",
},
data : data
});
</script>

angularjs 里面可以设置 请求头
其中 如果没设置 data数据的情况下 头信息设置 Content-Type 无效