利用 JSONP 实现跨站请求的方法及原理

目录:

因为之前想解决博客前后端对接时候跨站请求的问题,于是看到可以用 JSONP 来实现,研究了一番之后做一下笔记。

实现方法

要实现跨站请求是需要同时牵扯到前后两端的代码的。

后端

首先需要修改后端,这边以 Flask 为例:

from functools import wraps
from flask import Flask, request, current_app, jsonify

app = Flask(__name__)


def support_jsonp(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        callback = request.args.get('callback', False)
        if callback:
            content = str(callback) + '(' + f(*args, **kwargs).data.decode('utf-8') + ')'
            return current_app.response_class(content, mimetype='application/json')
        else:
            return f(*args, **kwargs)

    return decorated_function


@app.route('/test')
@support_jsonp
def test():
    return jsonify({'abc': 1, 'def': 2})

这边 support_jsonp 装饰器会判断是否是 JSONP 请求,如果是,则返回相应的「特殊」数据(具体在下面原理的地方详细讲),否则就按正常的请求处理。

前端

后端改完之后,前端就可以来请求了,这边先以 jQuery 的 ajax 函数为例:

$.ajax({
    url: 'http://api.example.com/test',
    dataType: 'jsonp',
    type: 'get',
    success: function(data) {
        console.log(JSON.parse(data));
    },
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        console.log('failed');
    }
});

这样就可以成功请求到跨站的 JSON 了。

原理

其实原理非常简单,就是利用了 script 标签可以获取其它站点的 JavaScript 代码并执行这一特点。

前面的示例中,前端发起请求后,观察后端的日志就会发现,请求时候的地址 /test 被修改成了形如 /test?callback=jsonp1470561184396 的地址,也就是加上了一个 callback 参数,这时候去看之前修改的后端代码的 support_jsonp 装饰器就会发现,后端这里实际上先检查请求参数里有没有 callback 这个参数,如果有的话,就会通过 content = str(callback) + '(' + f(*args, **kwargs).data.decode('utf-8') + ')' 这一句来把 callbackf(*args, **kwargs) 返回的 JSON 数据拼接起来形成形如 jsonp1470561184396({"abc": 1, "def": 2}) 这样的「数据」,然后把这个「数据」返回给浏览器。这里的「数据」之所以加引号,是因为其实这里拼接出来的 content 已经不再是单纯的 JSON 数据,而是一个 JavaScript 函数调用,而这里面形如 jsonp1470561184396 的东西就是 ajax 自动给回调函数编的号。另外,这里被修改后的 /test?callback=jsonp1470561184396 这个链接实际上被放在了一个 script 标签的 src 属性里,于是:script 标签从 http://api.example.com/test?callback=jsonp1470561184396 获取到 JavaScript 代码 jsonp1470561184396({"abc": 1, "def": 2}) 然后执行,成功调用回调函数。

理解了这个之后就可以比较随心所欲的使用了,无论代码怎么写只要最终后端拼接出了相应的函数调用代码就行,你甚至可以在 HTML 里面这么写:

<script src="http://api.example.com/test?callback=console.log"></script>

参考资料

评论