在大多数的 Web 项目中(以 JavaWeb 为例),网站的页面和服务(至少是 controller 层)在同一个工程进行开发和部署,在大前端的新型模式下,我们建议尽可能对网站的前端和后端进行完全分离,前后端分离的好处和意义这里不再赘述。
既然是前后端分离,那么部署也必然是各自独立部署,不同的访问路径,就会产生跨域访问的问题(同一站点,不同端口号也是跨域)
在此设定背景情况:
服务端已完整开启 CROS 跨域支持
http 组件使用 axios
axios 设置withCredentials为 true 开启跨域访问时携带 cookie 数据
高版本浏览器(ie10+ 或 chrome, ff)仅需要完成背景情况中的功能,即可支持跨域数据请求功能
axios 进行数据请求时,默认使用XMLHttpRequest对象,在检测到当前请求是跨域访问时,axios 会测试浏览器是否支持XDomainRequest对象,若支持则优先使用。
ie8 / ie9 的XMLHttpRequest对象,不支持跨域访问,该对象在 ie10 后才原生支持跨域访问。微软的解决方案是在 ie8 / ie9 中提供了XDomainRequest(XDR) 对象来进行解决跨域问题,虽然使用该对象可以跨域访问成功,并返回数据,但它却依然是一个功能不完整的半成品,它的使用有诸多限制:
XDR 仅支持 GET 与 POST 两种请求方式
XDR 不支持自定义的请求头,若服务端使用header的自定义参数进行做身份验证,则不可用
请求头的Content-Type只允许设置为text/plain
XDR 不允许跨协议的请求,如果网页在 HTTP 协议下,就只能请求 HTTP 协议下的接口,不能访问 HTTPS 接口
XDR 只接受HTTP/HTTPS 的请求
发起请求的时候,不会携带authentication或cookies
微软虽然提供了解决方案,但却是不折不扣的鸡肋,根本无法胜任系统中各种场景的数据请求需求,至此,axios 对 ie9 的跨域数据请求已无能为力。
完美解决方案:代理(proxy)
虽然 axios 对 ie9 跨域已无能为力,但前端项目打包的解决方案webpack提供了一个优雅而彻底解决问题的方式:代理
devServer.proxy
webpack 的devServer.proxy的功能是由http-proxy-middleware项目来实现的
实现原理是将目标位置的请求代理为前端服务本地的请求,既然是代理成为本地的请求,就不存在跨域的问题,axios 就会用回XMLHttpRequest对象进行数据请求,一切都恢复正常了,header、cookies、content-type、authentication 等内容都被正确传递到服务端。
项目中 webpack.config.js 的配置
配置中指定了将http://localhost:8081/myserver服务的位置代理为本地前端服务的http://localhost:8080/api。例如需要读取用户信息的原请求是http://localhost:8081/myserver/user/zhangsan,代理后,就变为http://localhost:8080/api/user/zhangsan。
即是/api的前缀代表了服务端,所以在使用 axios 时,需要对每个服务端请求都增加上/api的前缀;通常在项目开发中,需要对数据请求组件axios进行二次封装,以达到统一设置默认参数,统一数据请求入口等目的,那么此时就只需要在二次封装的文件里统一调整请求前缀即可。
不过,webpack 的devServer.proxy仅在开发模式下可用,生产模式下无法使用。开发模式下,调试服务可以读取webpack.config.js中的配置内容进行实时代理,而项目在部署到生产环境前,需要将工程进行编译转换成静态的 js 文件,没有调试服务的支撑自然是无法进行请求代理的。
nginx 配置
虽然devServer.proxy的功能仅能工作于开发模式,那么在生产模式下,自然也是有解决方案的;通常 Vue 的项目在编译成最终的 js 文件后,仅需要静态服务器即可,这其中又以nginx为最优选择方案,轻量、高性能、高并发、反向代理服务等均为其优点,这里需要做的数据请求代理的功能就使用到了 nginx 的 反向代理 功能
conf/nginx.conf文件配置增加以下内容
location /api/ { proxy_pass http://localhost:8081/myserver/; }
该配置同样是将http://localhost:8081/myserver/的目标服务端位置代理为本地服务的/api路径,如此,生产环境下的数据请求问题也得以解决。