了解跨域资源共享

1、背景

    之前在完成一个web项目的时候,采用了前后端分离的模式,后端采用了Flask搭建服务,前端基于React框架搭建界面。后台服务部署在了阿里云服务器,使用git来进行项目代码的更新,但是在前端通过Ajax调试后台接口的时候,却一直报错,对于第一次尝试前后分离的我们来说打击不小,最后对错误代码的分析,原来是跨域的问题。

1
2
//错误代码
**XMLHttpRequest cannot load api.jxnugo.com/course. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '' is therefore not allowed access.**

2、什么是跨域

    要说跨域,首先要了解Ajax(Asynchronous JavaScript+XML),它的核心是XMLHttpRequest(XHR)对象,这个技术使得我们的界面无需刷新便可以异步地从服务器获得数据,从而更新DOM。

    跨域给开发者带来了很多的方便,同时也出现了一些弊端,XHR对象是实现Ajax通信的主要方式,而这样就带来了跨域安全的问题。在默认的情况下,XHR对象只能访问与包含它的页面处于同一个域中的资源,也就是在协议,域名,端口完全相同的情况中,是不会出现跨域的问题。这样做的目的是为了保证页面的安全性,防止界面受到外部资源的干扰,比如恶意代码的注入。但是不可避免的,在实际使用中,有时还是需要使用一些其它的外部资源,为了解决这个问题。w3c就制定了一套标准,使得浏览器和服务器之间在跨域可以相互沟通。

3、跨域协商机制

    每一次Ajax请求都会发送HTTP请求,因为,协商机制也就体现在了HTTP报文中,HTTP头部定义了很多可以用来浏览器和服务器协商的字段,通过这些字段,能让服务器对浏览器的请求作出成功或者失败的响应。下面来了解一些浏览器和服务器端经常使用的一些http首部字段:

    浏览器端http报文中一些常见的头部如下:

  • Accept 浏览器能够处理的内容类型,比如Json
  • Accept-Charset 浏览器能够处理的字符类型
  • Accept-Encoding: 能够处理的压缩编码类型,如gzip
  • Accept-Language: 浏览器当前设置的语言
  • Connection: 连接类型,比如keep-alive 长连接
  • Content-type: 内容的类型
  • Host: 目标主机名
  • Origin: 源
  • Referer: 发出请求页面的url

    举个浏览器请求头部的例子

浏览器请求

    服务器端http报文中一些常见的响应头部如下:

  • Access-Control-Allow-Origin: 允许哪个域对资源进行访问,允许全部用*
  • Connection连接的类型
  • Content-Encoding: 编码格式,压缩数据有利于减少阐述时间,浏览器接受后解码
  • Server: 服务器类型
  • Vary:主要用户缓存服务器对于内容压缩的使用,服务器使用浏览器能接受的压缩方式

    举个服务端响应的例子

服务端响应请求

    从上面可以看出,浏览器和服务器对于跨域的协商主要是在Access-Control-Allow-Origin这个值的设置,服务端只有设置了这个字段,才能对资源进行跨域访问。

4、设置Access-Control-Allow-Origin

    在对跨域有了一定的了解之后,对自己项目进行http报文头部分析,发现请求和相应并不在同一个域,而服务端也并未对跨域的相关协商字段进行设置,导致了程序的报错。
    结合下面的图可以看出,不在同一个域
错误的原因
弄清楚了原因,解决方法也很容易就想到了,只要要设置服务端跨域响应的字段,在python中使用装饰器的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
from functools import wraps
from flask import make_response

def allow_cross_domain(fun):
@wraps(fun)
def wrapper_fun(*args, **kwargs):
req = make_response(fun(*args, **kwargs))
req['Access-Control-Allow-Origin'] = '*' # '*'代码允许任意请求
req['Access-Control-Allow-Methods'] = 'PUT,GET,POST,DELETE'
req['Access-Control-Allow-Headers'] = allow_headers
return req
return wrapper_fun

    在路由下添加装饰器即可,如果觉得麻烦,在Flask中也可以使用一个第三方插件,fLask-cors。当然,这里只介绍了一种方式,其实还有很多方式可以解决跨域的问题,比如在Nginx代理中设置Access-Control-Allow-Origin字段,或者使用JSONP,WebSocket都可以。

5、参考

热评文章