跨域问题是在 web 开发中经常遇到的一个问题。常见的解决方案网上罗列了很多,据了解,大家目前常用的是 JSONP 的方式。但这种方式只能使用 GET 方式,存在一定的局限性。所以未来更多的跨域场景,应该会使用 CORS 的方式实现。这里使用 Flask 框架作为服务端,模拟跨域场景,并使用 CORS 方式解决所遇到的跨域问题。
CORS 的具体概念,可以参考MDN和阮一峰博客的相关文章(在这里和这里),本文不再花费文字赘述。这里直接给出代码示例和讲解。
首先,我们要创建两个服务端,模仿实际场景中一个作为页面自身的服务端,我们称为 服务器A,一个作为被请求数据的服务端,我们称为 服务器B。代码其实一样,唯一区别是启动时所填写的 host 不同。
import json
from functools import wraps
from flask import Flask, make_response, render_template, request
app = Flask(__name__)
def cors(func):
@wraps(func)
def wrapper_func(*args, **kwargs):
r = make_response(func(*args, **kwargs))
r.headers['Access-Control-Allow-Origin'] = '*'
r.headers['Access-Control-Allow-Methods'] = 'HEAD, OPTIONS, GET, POST, DELETE, PUT'
allow_headers = "Referer, Accept, Origin, User-Agent, X-Requested-With, Content-Type"
r.headers['Access-Control-Allow-Headers'] = allow_headers
return r
return wrapper_func
@app.route('/cors', methods=['POST', 'GET'])
@cors
def domains():
return json.dumps({'data': 'data from A by {}'.format(request.method)})
@app.route('/')
def main():
return render_template('index.html')
if __name__ == '__main__':
app.run(host='localhost', port=8080)
上面是 服务器A 的具体代码
import json
from functools import wraps
from flask import Flask, make_response, render_template, request
app = Flask(__name__)
def cors(func):
@wraps(func)
def wrapper_func(*args, **kwargs):
r = make_response(func(*args, **kwargs))
r.headers['Access-Control-Allow-Origin'] = '*'
r.headers['Access-Control-Allow-Methods'] = 'HEAD, OPTIONS, GET, POST, DELETE, PUT'
allow_headers = "Referer, Accept, Origin, User-Agent, X-Requested-With, Content-Type"
r.headers['Access-Control-Allow-Headers'] = allow_headers
return r
return wrapper_func
@app.route('/cors', methods=['POST', 'GET'])
@cors
def domains():
return json.dumps({'data': 'data from B by {}'.format(request.method)})
@app.route('/')
def main():
return render_template('index.html')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8088)
上面是 服务器B 的具体代码,可以看到,除了 host 不同之外,没有任何区别。
接下来我们创建页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CORS</title>
</head>
<body>
<input id="id-input-url">
<button class="button-send" data-method="GET">get</button>
<button class="button-send" data-method="POST">post</button>
<script>
var cors = function (method) {
var url = document.getElementById('id-input-url')
const xhr = new XMLHttpRequest()
xhr.open(method, url.value, true)
if (method.toUpperCase() === 'POST') {
// set this if the method is POST
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
alert(xhr.responseText)
}
}
xhr.send()
}
var button = document.getElementsByClassName('button-send')
var bl = button.length
for (var i = 0; i < bl; i++) {
button[i].addEventListener('click', function () {
cors(this.dataset.method)
})
}
</script>
</body>
</html>
可以看到,页面中 Javascript 代码其实与我们平常所写的 ajax 请求过程没有什么区别。因为在 CORS 中,跨域问题其实主要是由服务端和浏览器完成,对于前端开发人员来讲,与普通不跨域的场景相比其实没有什么区别。
使用 python3 a.py 和 python3 b.py 启动两个服务器代码,然后访问 http://localhost:8080,在页面中 input 元素内填写 http://127.0.0.1:8088/cors,并点击 GET 按钮,测试一下,是否正常的弹出了警告框,显示 {'data': 'data from B by GET'}。当然,POST 按钮得到的结果也是一样,只是最后的 GET 被替换成了 POST。
那么这里我们就已经实现了针对 GET方法 和 POST方法 的跨域。那么在某些情况下,可能在跨域时,需要提交 cookie 信息,怎么办呢?
其实很简单,只要在服务端的 cors 装饰器中,设置响应头时,Access-Control-Allow-Origin 字段填写对方的完整 url,并且增加 Access-Control-Allow-Credentials 字段,设置为 'true'。然后在页面中,xhr 对象 send 前,增加 withCredentials 并设为 true 即可。具体修改部分的代码如下:
def cors(func):
@wraps(func)
def wrapper_func(*args, **kwargs):
r = make_response(func(*args, **kwargs))
r.headers['Access-Control-Allow-Origin'] = 对方的完整 url
r.headers['Access-Control-Allow-Methods'] = 'HEAD, OPTIONS, GET, POST, DELETE, PUT'
allow_headers = "Referer, Accept, Origin, User-Agent, X-Requested-With, Content-Type"
r.headers['Access-Control-Allow-Headers'] = allow_headers
# if you need the cookie access, uncomment this line
r.headers['Access-Control-Allow-Credentials'] = 'true'
return r
return wrapper_func
var cors = function (method) {
var url = document.getElementById('id-input-url')
const xhr = new XMLHttpRequest()
xhr.open(method, url.value, true)
xhr.withCredentials = true
if (method.toUpperCase() === 'POST') {
// set this if the method is POST
// IF YOU WANT TO POST WITH COOKIE
// IT MUST BE "application/x-www-form-urlencoded"
// DO NOT CHANGE THIS VALUE !!!!
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
alert(xhr.responseText)
}
}
xhr.send()
}
如此一来,即可实现带 Cookie 的跨域了。
有几个需要注意的地方:
-
在 cors 装饰器中,填写
对方的完整 url时,一定要写明协议,http://或https://。如果端口号不同,一定要带上端口号,并且不要添加最后一个/,举个例子,应该这样写:http://127.0.0.1:8088 -
Javascript 中,使用 POST 带 Cookie 跨域时,
Content-type一定要设置成application/x-www-form-urlencoded,否则还是会跨域失败。