最近,我们对跨域请求有需求,于是我今天下午和晚上花了大概四个小时左右调通了前端和后端。在整个过程中,大部分时间都花在找资料上,深感网上的资料太零散,比较难找,所以整理一番,分享给大家。

在正文之前,如果你对CORS不甚了解,请先阅读阮一峰老师的跨域资源共享 CORS 详解,文章非常的透彻全面。(汗!我google出来看的第一篇文章是另外一篇,整篇文章看了之后,知道的大概,但是完全不知道如何下手。后来是多番查找代码,各种试错,基本上搞定的时候,才看到阮一峰老师的这篇文章)。

好,咱们正文开始!

一、服务器配置

环境

语言:php
框架:CI + codeigniter-restserver

CROS请求分成简单请求和非简单请求。而我们在整个前后台通信中,数据交互格式是JSON的,而且存在许多除HEAD、GET、POST之外的请求,所以我们的请求都是非简单请求。而且,我们在CI中是通过session验证权限的,所以需要传递cookie

针对上面这两点要求,我们有以下配置:

1、对普通的GET/POST/PUT请求,请求头设置如下:

1
2
3
4
5
6
7
8
//设置json格式请求头
header("Content-type:application/json; charset=utf-8");
//跨域请求允许的域名设置,因为需要传递cookie,不能使用*
header("Access-Control-Allow-Origin: http://www.beyondwinlaw.com");
//跨域请求允许的请求头
header("Access-Control-Allow-Headers: Content-type");
//跨域请求同意发送Cookie
header("Access-Control-Allow-Credentials: true");

2、 非简单请求每次请求前,都会发送一个一次”预检“请求,它是options的请求方式。它主要是询问服务器是否允许这个非简单请求访问,如果我们允许,则返回所需要的回应头信息(response header),这个预检请求的请求头设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
//设置json格式请求头
header("Content-type:application/json; charset=utf-8");
//跨域请求允许的域名设置
header("Access-Control-Allow-Origin: http://www.beyondwinlaw.com");
//跨域请求允许的请求头
header("Access-Control-Allow-Headers: Content-type");
header("Vary: Accept-Encoding, Origin");
//跨域请求同意发送Cookie
header("Access-Control-Allow-Credentials: true");
//options请求中所允许的方法
header("Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS");
//OPTIONS这个预请求的有效时间,20天
header("Access-Control-Max-Age: 1728000");

在配置上面之前,我们还需要对预检请求做出回应。而在[codeigniter-restserver(https://github.com/chriskacerguis/codeigniter-restserver)框架中,使用的是RESTful API的风格,get_nameoptions请求方法是下面这么写的:

1
2
public funtion get_name_options() {
}

当然,我们不可能对所有的方法都写一个对应的options请求方法,那如何做呢?我们是在early_checks()做这件事情:

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
/**
* 请求之前检查,每个接口都会处理
*/
public function early_checks()
{
parent::early_checks(); // TODO: Change the autogenerated stub
/**
* 将get或post请求中的参数为空/两边有空格的数据处理一下
*/
switch ($this->request->method) {
case "get": {
$this->_get_args = $this->handle_param($this->_get_args);
}
break;
case "post": {
$this->_post_args = $this->handle_param($this->_post_args);
}
break;
case "options": {
//options请求,直接成功, 它设置了允许的方法
header("Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS");
//OPTIONS这个预请求的有效时间,20天
header("Access-Control-Max-Age: 1728000");
//做出回应
answer(true, '成功');
}
break;
}
}

注:answer方法中是统一的回应方法,内部已经设置了统一的请求头。

二、Angular中的配置

Angular中的配置更简单了,只需要对$http的GET/POST/PUT请求中的配置项中,将withCredentials设置为true就行了。

请求格式如下:

1
2
3
$http.get(url, [config]).success(function(){ ... });
$http.post(url, param, [config]).success(function(){ ... });
$http.put(url, param, [config]).success(function(){ ... });

具体代码如下:

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
//GET请求
$http.get('/test?name=Jack', {
withCredentials: true
}).success(function (data) {
console.log(data);
}).error(function (data) {
console.log(data);
});
//POST请求
$http.post('/test', {name: 'Jack'}, {
withCredentials: true
}).success(function (data) {
console.log(data);
}).error(function (data) {
console.log(data);
});
//PUT请求
$http.put('/test', {name: 'Luck'}, {
withCredentials: true
}).success(function (data) {
console.log(data);
}).error(function (data) {
console.log(data);
});

还需要提一下的是,我们在使用ng-file-upload进行上传时,设置withCredentials为true可以如下:

1
2
3
4
5
6
7
8
9
10
11
Upload.upload({
url: ServerURL + 'file/file_upload',
data: { file: file },
withCredentials : true
}).progress(function (evt) { // 进度
console.log('进度: ' + parseInt(100.0 * evt.loaded / evt.total) + '%');
}).success(function(data, status, headers, config) {
console.log(data);
}).error(function(data) {
console.log(data);
});

总结

虽然折腾了很久,其实真正知道了,配置起来特别的简单。所以能找到一篇好的文章,是真的很幸福的一件事情。

参考

跨域资源共享 CORS 详解

陈斌彬的技术博客

HTTP访问控制(CORS)

Angular通过CORS实现跨域方案

withCredentials is not working