关于预检请求
定义:预检请求(Preflighted requests)是浏览器发起跨域请求时,通过OPTIONS方法询问服务器对跨域请求的支持情况(支持的包含请求方法、请求头、数据类型)。
触发预检请求的三类条件:
-
默认情况下,跨域请求只支持GET,HEAD,POST方法,如果不是这三个请求方法(比如:PUT、DELETE、CONNECT、OPTIONS、TRACE和PATCH),那么将触发预检请求
-
默认情况下,浏览器跨域请求时,会自动添加的请求头(HOST,Referer,Connection、Accept、User-Agent,Accept-Languange,Accept-Encoding,Accept-Charset和Content-Type),这些请求中还有其他请求头时,那么将触发预检请求。
-
如1、2所说的情况排除在外的条件下,跨域请求是,浏览器支持的Content-Type值为application/x-www-form-urlencoded,multipart/form-data和text/plain。如果是其他数据类型(如application/json,text/xml...),那么将触发预检请求。
下面通过一个Ajax跨域请求来验证
varxhr=newXMLHttpRequest(); varurl='http://bar.other/resources/post-here/'; varbody='<?xmlversion="1.0"?><person><name>Arun</name></person>'; functioncallOtherDomain(){ if(invocation) { xhr.open('POST',url,true); xhr.setRequestHeader('X-PINGOTHER','pingpong');//自定义的Header xhr.setRequestHeader('Content-Type','application/xml');//特殊的文档类型 xhr.onreadystatechange=function(){}; xhr.send(body); } }
满足以上之一,便可发起预检请求,预检请求流程如下
OPTIONS/resources/post-here/HTTP/1.1 Host:bar.other User-Agent:Mozilla/5.0(Macintosh;U;IntelMacOSX10.5;en-US;rv:1.9.1b3pre)Gecko/20081130Minefield/3.1b3pre Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language:en-us,en;q=0.5 Accept-Encoding:gzip,deflate Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection:keep-alive Origin:http://foo.example Access-Control-Request-Method:POST Access-Control-Request-Headers:X-PINGOTHER,Content-Type HTTP/1.1200OK Date:Mon,01Dec200801:15:39GMT Server:Apache/2.0.61(Unix) Access-Control-Allow-Origin:http://foo.example Access-Control-Allow-Methods:POST,GET,OPTIONS Access-Control-Allow-Headers:X-PINGOTHER,Content-Type Access-Control-Max-Age:86400 Vary:Accept-Encoding,Origin Content-Encoding:gzip Content-Length:0 Keep-Alive:timeout=2,max=100 Connection:Keep-Alive Content-Type:text/plain POST/resources/post-here/HTTP/1.1 Host:bar.other User-Agent:Mozilla/5.0(Macintosh;U;IntelMacOSX10.5;en-US;rv:1.9.1b3pre)Gecko/20081130Minefield/3.1b3pre Accept:text/html,*;q=0.7 Connection:keep-alive X-PINGOTHER:pingpong Content-Type:text/xml;charset=UTF-8 Referer:http://foo.example/examples/preflightInvocation.html Content-Length:55 Origin:http://foo.example Pragma:no-cache Cache-Control:no-cache <?xmlversion="1.0"?><person><name>Arun</name></person> HTTP/1.1200OK Date:Mon,01Dec200801:15:40GMT Server:Apache/2.0.61(Unix) Access-Control-Allow-Origin:http://foo.example Vary:Accept-Encoding,Origin Content-Encoding:gzip Content-Length:235 Keep-Alive:timeout=2,max=99 Connection:Keep-Alive Content-Type:text/plain [SomeGZIP'dpayload]
我们看到,首次发起的是OPTIONS请求,因为OPTIONS请求的作用本身就是询问服务器的请求,他这里询问浏览器是否支持如下条件请求
Access-Control-Request-Method:POST #是否支持POST Access-Control-Request-Headers:X-PINGOTHER,Content-Type #是否支持X-PINGOTHER自定义请求头的内容,Content-Type可能是其他值,因此有必要询问
服务器回应是
Access-Control-Allow-Origin:http://foo.example #支持foo.example域 Access-Control-Allow-Methods:POST,OPTIONS #支持的方法POST,OPTIONS,实际上OPTIONS本身就支持 Access-Control-Allow-Headers:X-PINGOTHER,Content-Type #支持的请求头,Content-Type是特殊的类型时,会触发预检,因此,这里最好加上Content-Type Access-Control-Max-Age:86400 #支持的缓存时间
如果满足以上条件,浏览器会自动发起提交之前没提交的数据,否则拒绝提交数据。
关于withCredentials
在跨域请求中,类似Cookie等敏感信息一般不会跨域传输,但是在服务器允许的情况下,Cookie会被发送
注意:Cookie的发送需要服务器允许才行,此外,跨域js所处的环境必须是线上环境【服务器环境】。
Access-Control-Allow-Credentials:true
浏览器也要允许
varxhr=newXMLHttpRequest(); varurl='http://bar.other/resources/post-here/'; varbody='<?xmlversion="1.0"?><person><name>Arun</name></person>'; functioncallOtherDomain(){ if(invocation) { xhr.withCredentials=true;#允许cookie信息 xhr.open('POST','application/xml');//特殊的文档类型 xhr.onreadystatechange=function(){}; xhr.send(body); } }
服务器设计建议
服务器设计,我们不仅要处理常见的跨域请求,对于Preflighted Request请求,我们更应该保证浏览器能得到预检请求的结果,因此,服务器端一定要处理OPTIONS请求