跨域问题是在 web 开发中经常遇到的一个问题. 常见的解决方案网上罗列了很多, 据了解, 大家目前常用的是 JSONP 的方式. 但这种方式只能使用 GET 方式, 存在一定的局限性. 所以未来更多的跨域场景, 应该会使用 CORS 的方式实现. 这里使用 Flask 框架作为服务端, 模拟跨域场景, 并使用 CORS 方式解决所遇到的跨域问题.
首先, 我们要创建两个服务端, 模仿实际场景中一个作为页面自身的服务端, 我们称为 服务器 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
, 否则还是会跨域失败.
来源: https://juejin.im/entry/5b2873c851882574e7270588