服务端跨域处理原理以及处理
背景
随着CROS标准的推广,跨域成了每个服务器开发者必须处理的问题,网上关于如何解决跨域问题的文章数不胜数,但是能用的少之又少,不少文章作者都不清楚跨域原理,不知从哪里抄来一字半句发现有效果就匆匆发表,误导了不知多少人。
其实处理跨域的原理并不复杂,本文会介绍处理跨域的原理并给出python实现。
跨域标准
CROS标准是W3C定义的新标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
讲解CROS标准的文章推荐阮一峰的《跨域资源共享 CORS 详解》,讲的比较清楚明了,本文不再赘述CORS标准,只讲服务端的处理
CORS标准需要浏览器支持,浏览器会自动按照CORS标准发出请求,服务器需要按照CROS标准给出对应的回复,否则浏览器拒绝继续HTTP请求。
新版本的浏览器基本都强制开启了跨域请求,所以除非你能限制浏览器,不然都免不了跨域这一关。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨域通信。
跨域也可以从浏览器关闭,但是目前都需要在浏览器启动传入参数或者修改一些内部配置,没有办法在前端代码里关闭。
所以网上所有说在web代码里处理跨域的都是在扯蛋,跨域请求必须由服务器作出处理。而所有说在服务端关闭跨域的也是在扯蛋,只能从浏览器端关闭跨域。
跨域处理
跨域处理分两种,简单请求和非简单请求。
怎么分辨是不是简单请求呢?其实很简单,同时满足以下全部条件的就是简单请求:
- HTTP请求方法只能是以下几种方法之一:
HEAD
GET
POST
- HTTP请求的头信息只有以下字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
- 如果HTTP请求的头信息有
Content-Type
,Content-Type
值只能是以下几种之一:application/x-www-form-urlencoded
multipart/form-data
text/plain
简单请求
浏览器在对一个url发起简单跨域请求时会在HTTP请求头中设置一个Origin
字段,表示浏览器发起请求的来源地址。
服务器需要检查这个来源是否合法,如果合法的话,服务除了需要正常处理业务以外,还需要在HTTP回复的响应头中设置如下字段:
Access-Control-Allow-Origin
- 值和请求头中的
Origin
字段值一致
- 值和请求头中的
Access-Control-Allow-Credentials
- 值一般设为字符串类型的
true
,表示是否允许发送Cookie
- 值一般设为字符串类型的
非简单请求
非简单请求的处理除了要完成上述对于简单请求的处理之外,还需要一些额外的处理。
浏览器在对一个url发起跨域非简单请求时首先会发出一个相同url的OPTIONS
方法的请求,一般我们称之为preflight
。然后根据preflight
请求的结果再进行实际的请求发送。
浏览器会在preflight
的HTTP请求头中设置一个Access-Control-Request-Method
字段,用来表示本来实际发起的请求是什么方法。
如果实际的请求头中包含自自定义的字段,那么浏览器在还会在preflight
的HTTP请求头中设置一个Access-Control-Request-Headers
字段,用来表示实际发起的请求头中将要包含的字段。
对于preflight
的处理,服务器此时应返回200
,并先对此请求进行简单请求的处理,然后再返回的请求头中设置如下字段:
-
Access-Control-Allow-Methods
- 此字段表示服务器针对此url允许哪些方法的请求,一般来说,值可以设为请求头中的
Access-Control-Request-Method
字段的值。但是最好每次都全部设为GET,POST,PUT,DELETE,OPTIONS
,这样可以避免浏览器针对每种方法都发起preflight
,提升性能。
- 此字段表示服务器针对此url允许哪些方法的请求,一般来说,值可以设为请求头中的
-
Access-Control-Allow-Headers
- 此字段表示服务器允许接收的请求头的字段,如果浏览器的请求头中有
Access-Control-Request-Headers
,则将值设置为和请求头中Access-Control-Request-Headers
字段的值一致。否则设为字符串类型的*
。
- 此字段表示服务器允许接收的请求头的字段,如果浏览器的请求头中有
当浏览器完成了preflight
请求并且服务器给出的响应都正确,浏览器才会发起真正的跨域非简单请求。此时的处理就比较简单了。此时浏览器应该先对此请求进行简单请求的处理,然后按照preflight
响应头中关于Access-Control-Allow-Headers
的处理规则进行处理即可。
python-bottle实现
通常来说,一个现代的、成熟的服务器框架应该可以按照配置自己处理CROS
,但是当框架没有提供时,就需要我们按照上文规范自己处理。
这里给出使用python的bottle框架的跨域处理。其他的框架、语言也可以参考以下实现
响应头处理
首先针对所有的请求,使用hook做统一的响应头处理
@ app.hook('after_request')
def enable_cors():
if 'Origin' in request.headers:
origin = request.headers['Origin']
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
if 'Access-Control-Request-Headers' in request.headers:
response.headers['Access-Control-Allow-Headers'] = request.headers['Access-Control-Request-Headers']
else:
response.headers['Access-Control-Allow-Headers'] = '*'
OPTION请求处理
然后针对所有OPTION
请求做处理
@ app.route(urlPrefix+'/<n:re:.+>', method='OPTIONS')
def options(n):
response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS'
return ''