浅谈跨域问题

跨域问题相信不少朋友都遇到过,八仙过海各显神通,解决的方法也有很多种,那我今天也来稍微扯一扯。

问题重现

先用nodejs写一个最简单的web后台

1
2
3
4
5
6
7
8
9
10
// server.js
var http = require('http');

http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
console.log('I have received your request.');
res.end('Hello World\n');
}).listen(6022, "0.0.0.0");

console.log('Server running at http://0.0.0.0:6022/');

在vps上运行以后web端请求正常:

在本地写一个html文件跨域请求

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- local.html -->
<html lang="en">
<head>
<script type="text/javascript" src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script>
</head>
<body>
<script>
$.get("http://x.x.x.x:6022", function(result){
console.log(result);
});
</script>
</body>
</html>

用chrome打开:

一个寻常的跨域问题就此构造完成。
note: 虽然跨域请求失败,但server端确实收到了请求,并成功打出了日志,可见浏览器对于跨域请求没有作限制,问题出在response到浏览器解析这一个环节。
使用charles抓包,发现hello world这条数据是成功被送到了客户端,那么就可以断定是浏览器在解析response头的时候作了限制。

问题解决

服务端方案

既然是Access-Control-Allow-Origin的问题,chrome的调试器已经说得很清楚了,那就从这里开刀
server端代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// server.js
var http = require('http');

http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain',
'Access-Control-Allow-Origin': '*' // 新加的代码
});
console.log('I have received your request.');
res.end('Hello World\n');
}).listen(6022, "0.0.0.0");

console.log('Server running at http:/0.0.0.0:6022/');

客户端请求结果:

跨域请求数据成功!

jsonp方案

jsonp方案也需要对server作一些修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// server.js
var http = require('http');

http.createServer(function (req, res) {
// 需要从request的请求中取出jsoncallback的值,并用它来构造返回数据
// jsoncallback的值是客户端动态生成的需要回调的函数
console.log(req.url); // /?jsoncallback=jQuery1910832485890481621_1441186464213&_=1441186464214
var query = req.url.split('?')[1];
var cb = query.split('&')[0].split('=')[1];
res.writeHead(200, {
'Content-Type': 'text/plain',
});
res.end(cb + "('Hello World')");
}).listen(6022, "0.0.0.0");

console.log('Server running at http:/0.0.0.0:6022/');
  1. 客户端方案1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- server获取的req.url: /?jsoncallback=jQuery1910832485890481621_1441186464213&_=1441186464214 -->
    <!-- local.html -->
    <script>
    $.getJSON("http://x.x.x.x:6022?jsoncallback=?",
    function(json){
    console.log(json); // Hello World
    }
    );
    </script>
  2. 客户端方案2
    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
    <!-- server获取的req.url: /?jsoncallback=jQuery19104387724103871733_1441186839654&{}&_=1441186839655 -->
    <!-- local.html -->
    <script>
    $.ajax({
    async:false,
    url: 'http://x.x.x.x:6022',
    type: "GET",
    dataType: 'jsonp',
    jsonp: 'jsoncallback',
    data: '{}',
    timeout: 5000,
    beforeSend: function(){
    console.log('beforeSend');
    },
    success: function (json) {//客户端jquery预先定义好的callback函数,成功获取跨域服务器上的json数据后,会动态执行这个callback函数
    console.log(json); // Hello World
    },
    complete: function(XMLHttpRequest, textStatus){
    console.log('complete');
    },
    error: function(xhr, a, b, c, d){
    //请求出错处理
    }
    });
    </script>
  3. 客户端方案3
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- server获取的req.url: /?jsonppppp=jsonpCallback -->
    <!-- local.html -->
    <script type="text/javascript">
    function jsonpCallback(result) {
    console.log(result);
    }
    </script>
    <script type="text/javascript" src="http://x.x.x.x:6022?jsonppppp=jsonpCallback"></script>

以上这三种客户端方法都能成功获取跨域数据,总结如下

  1. 方案一和方案二本质上是一样的,方案一是方案二的一个高级封装
  2. jsonp只能是http get请求
  3. 三种方法的req.url不全相同
  4. 前两种方案的前端js回调函数是自动生成的,每次请求都不同,有一个全局id。第三种则没有,每次请求都是同一个函数对象