分类
http

解决跨域问题笔记

跨域问题常遇常新,每次都觉得再也不会有问题了,结果过几天又会掉进新坑。

因为种种原因,最近一个项目需要跨域请求 API。然后就随手设置了一下,结果 GET 没问题,POST 就不行,很明显是撞到跨域墙上了。

最后发现原因:

  1. 我们启用了 basic auth 验证用户身份
  2. OPTIONS 也会被要求验证
  3. 预请求失败,后面的正式请求就不会发出

趁着还没忘,总结一下跨域的处理过程:

  1. 首先,熟读《MDN HTTP访问控制(CORS)》
  2. 跨域时,复杂请求(除 HEADGETPOST) API 需要返回 CORS 头
  3. 发起复杂请求前,会发送一个 preflight 请求,也就是 OPTIONS,很多坑都在这个请求上
  4. OPTIONS 是浏览器自动发送的,不受我们控制,在开发者工具的 Network 面板里也看不到。我们经常需要模拟它,检查返回是否符合预期。请求头在后面。
  5. OPTIONS 无法处理 Basic auth,如果开了的话,要做特殊处理
  6. 需要返回 Access-Control-Allow-Origin 允许跨域的域名,简单点可以写 *,但如果要上传 cookie,则必须写明域名,且只能是 一个 域名
  7. 所以如果有多个域名要跨域访问 API,需要在服务器端判断来源,并返回不同的域名
  8. 如果要上传 cookie,需要在请求时声明 withCredential: true
  9. 服务器还要返回许可的方法,即 Access-Control-Allow-Methods: GET, DELETE, PATCH 等,让浏览器判断
  10. 如果前面都通过了,浏览器才会发送正式请求。如果正式请求失败,则看不到任何返回。

测试 OPTIONS 请求头

OPTIONS /resource/foo 
Access-Control-Request-Method: DELETE 
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
分类
jQuery php

HTML5跨域开发

HTML5中提供了跨域加载数据的方法,让我们得以从JSONP或者Flash中介等各种绕行方案中解脱出来,更加顺畅地与服务器交流。另一方面,因为PHP是最好的语言……所以在它与Node.js之间,我选择前者作为后端语言开发内容服务。

这篇文章记录使用jQuery+PHP开发跨域应用时的小心得。

身份验证

做身份验证,最简单的办法就是使用PHP的SESSION保存用户信息,于是就要用到Cookie。默认情况下,跨域Ajax请求发起时候不包含Cookie,需要我们主动将XHRwithCredentials属性设为true才行。

jQuery会把XHR封装成jqXHR,并且不暴露真正的XHR(说实话这点有点难以理解,尤其是在做上传进度条的时候)。然后它提供一个给真正XHR赋值的接口xhrField,所以写成代码就是这样事儿的:

$.ajax(url, {
  xhrField: {
    withCredentials: true
  }
}

各种HTTP头

如果不需要验证用户身份,直接在HTTP头中输出Access-Control-Allow-Origin: *即可。

我的产品需要验证,那么首先,HTTP头中必须有Access-Control-Allow-Credentials: true;此时对域的限制也严格许多,不再允许像前面那样使用*放开给任何来源,必须指明哪个具体域可以接受。

关于Access-Control-Allow-Origin的值,规范中的说明是“域名列表或null”,然则接下来的“注意”有点诡异:“实际生产中,‘列表或null’要求更严格。你可以认为它实际只允许单一域名或null,而非空格分隔的域名列表。”——既然如此你干脆写个“域名或null”不就完了……

总之对于我们而言,返回的HTTP头中还要包含Access-Control-Allow-Origin: http://域名,指定允许作为来源的协议、域名、端口,并且只能有一个(组)。因为通常来说我们开发环境和生产环境不一样,所以这里的域名最好不要写在服务器配置里;使用PHP,通过$_SERVER['HTTP_ORIGIN']取出访问来源,与白名单比对,通过后再输出相应的头,更加合适。

调试

我选择JSON作为前后端交流的格式。为了方便浏览器解析(也是HTML5的要求),我还返回了Content-type: application/json头。

使用PHP少不了使用Xdebug。出现错误时,Xdebug会返回完整的栈,有利排查。但是为了方便阅读,Xdebug还会给返回信息套上<table>结构,这时Chrome的Network工具就会把它解析成奇怪的格式,所以Content-type一定要最后和数据一起返回。

与之相反的是前文说到的Access-Control-Allow-OriginAccess-Control-Allow-Credentials,这二位必须放在最前面。不然如果出现500错,响应头不包含这两个跨域标记,Chrome就会理所当然地不显示返回内容,也就无法看到错误描述,根本无法排查。

参考资料

  1. Using CORS
  2. Cross-Origin Resource Sharing
  3. HTTP access control (CORS)
  4. jQuery.ajax()