非同源域名axios无法携带cookie,基于问题我们深挖axios

1,895 阅读6分钟

1.缘起非同源,根在axios

1.同域带cookie,非同域选择性带

  • 真实项目中,突然由于变动需要访问非同域的域名,按理说会跨域。
  • 由于是公司内网,也他们最后的根域名都是一样的,请求并没有跨域,但是cookie却无法携带。
  • 我们尝试自己请求的时候携带cookie,发现原因自己携带,也会不生效
axios({
	url:a.com,
	headers:{
		Cookie:"aaaaaaaaaa"
	}
})

2.域名服务器之间的映射

  • 域名和服务器之间是多对一,服务器地址ip是唯一的,用户访问域名,域名通过解析到服务器对应的的端口。
  • 由于cookie是种在根域名下,也就是类似服务器的ip,所以这两个域请求并不会跨域
  • 产生的真正原因是,以为axios检测不到是不是根域名下,所以处于保护措施,axios会自动禁止非同源请求携带cookie的, 请添加图片描述

3.axios的withCredentials属性解决问题

  • withCredentials 默认为fasle
  • withCredentials属性可以允许非同源的域名携带cookie
  • 当我们开启以后就可以携带cookie了
axios.defaults.withCredentials = true
// 后端如果没开启的话,也需要开启下

2.axios我们最熟悉的陌生人(手动封装)

1.ajax四部曲

  • 创建一个XMLHttpRequest类
  • 用什么方式来打开来访问这个URL以及是否为异步
  • 监听这个实例的状态变化
  • 然后调用这个实例来发送数据
var ajax = new XMLHttpRequest();
ajax.open(method, url, true);
ajax.onreadystatechange = function() {
    if(ajax.readyState === 4 && ajax.status === 200){
      // 处理哦返回逻辑
    }
}
ajax.send()

2.基于promise解决异步并发问题

  • promise作为解决回调地狱的问题和并发的最优解,我们利用特性去封装
  • Axios({...}).then(res=>{...});直接用默认配置的封装
function Axios(obj) {
    const {method,url, params} =obj;
    return new Promise(function(resolve,reject) {
        var ajax = new XMLHttpRequest();
        ajax.open(method, url, true);
        ajax.onreadystatechange = function () {
            if (ajax.readyState === 4 && ajax.status === 200) {
                // 处理返回逻辑
                let data = JSON.parse(ajax.responseText)
                resolve(data)
            }
        }
        ajax.send(params)
    }).catch(err=>{
     resolve(err)
    })
}

3. new 一个Axios_ 类,并实现我们想要的

  • 我们需要先构造一个类
  • 我们只模拟核心逻辑,所以我们在类的原型上,需要提供get,post,create,interceptors这四种方法
 class Axios_{
        constructor(props){// 把需要暴露出去的api进行return }
        interceptors={// 里面包含了请求拦截器和响应拦截器}
        myAxios(obj){ // 这里放我们刚才封装的异步axios}
        get(...arg){// get方法 } 
        post(...arg){// post 方法}
        create(...arg){// 创建实例方法 } 
}

Axios.create({})会返回一个可配置的axios实例

create(...arg){
    return new Axios_(...arg)
} 

get 和post 等均需需要调用我们封装的promise版ajax

get(...arg){
    const [url,params] = arg;
    console.log(url,params,arg);
    return this.myAxios({method:'get',url,params})
} 
post(...arg){
    const [url,params] = arg;
    return this.myAxios({method:'post',url,params})
}

constructor中将方法报露出来

constructor(props){
    return {
        get:this.get,
        post:this.post,
        create:this.create,
        myAxios:this.myAxios,
        interceptors:this.interceptors
    }
}

4.基于实例可配置拦截器

我们先看下拦截器的基本使用

  • 请求执行前,可对请求进行处理和配置
  • 数据响应之后发送到前端之前,可对结果进行处理和配置
let http = axios.create({})
http.interceptors.request.use((config)=>config)
http.interceptors.response.use((response)=>response)

Axios.interceptors中包含请求拦截器和响应拦截器、 根据调用我们构造interceptors对象

interceptors={
    request:{
       use:(obj)=>obj
    },
    response:{
       use:(obj)=>obj
    }
}

myAxios 利用我们封装的promise 请求拦截器在生成实例前,执行请求拦截器,在响应时先经过响应拦截器 注意promise闭包里的this,由于this指向问题,所以需要存储下this

myAxios(obj){
    // 请求拦截器
    const beforeRequetMap = this.interceptors.request.use(obj)
    const {method,url, params} =beforeRequetMap;
    const that =this ; //  作用域存储
    return new Promise(function(resolve,reject) {
        var ajax = new XMLHttpRequest();
        ajax.open(method, url, true);
        ajax.onreadystatechange = function () {
            if (ajax.readyState === 4 && ajax.status === 200) {
                let data = JSON.parse(ajax.responseText);
                // 响应拦截器
                console.log(this,that,'this');
                const beforeResponseMap = that.interceptors.response.use(data)
                resolve(beforeResponseMap || data)
            }
        }
        ajax.send(params)
    })
} 

5. withCredentials的庐山真面目

cookie是根据种在域名下cookie去携带的。 也就是说,所有的设置配置cookie的方法,全是基于document.cookie去设置的 withCredentials 也是基于ajax的withCredentials属性去透传并做了一些判断。 这边判断了withCredentials是否为是或者是不是非同源,从而去判断是否去写入cookie 下面我们看下源码

if (utils.isStandardBrowserEnv()) {
   var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
     cookies.read(config.xsrfCookieName) :
     undefined;
   if (xsrfValue) {
     requestHeaders[config.xsrfHeaderName] = xsrfValue;
   }
}
// cookies.read
read: function read(name) {
   var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'));
   return (match ? decodeURIComponent(match[3]) : null);
 },

我们需要模拟这个过程,axios调用

axios.get('./a.json',{
	headers:{
		cookie:'document.cookie'
	}
})

axios类上增加defaults,存储了一些默认配置 isURLSameOrigin方法回去判断baseURL是否是同域 setCookie 通过循环拼接将传递过来的cookie进行拼接

const.defaults={
	withCredentials:false
}
isURLSameOrigin(){...}
setCookie(){...}
...
myAxios(obj){
    // 请求拦截器
    const beforeRequetMap = this.interceptors.request.use(obj)
    const {method,url, params} =beforeRequetMap;
    const that =this ; //  作用域存储
    return new Promise(function(resolve,reject) {
        var ajax = new XMLHttpRequest();
        ajax.open(method, url, true);
        if(that.defaults.withCredentials || this.isURLSameOrigin(url)){
	        this.setCookie(params?.headers?.cookie)
        }
	     // 省略无关代码
        ajax.send(params)
    })
} 

3.蓦然回首整体思路

1.先解决当前问题,找到问题所在

  • withCredentials默认为false,
  • withCredentials 并不会考虑到内网及其多域名打到统一服务器上的异常场景
  • 按理说,就算允许跨域携带cookie,但是大多数还是不一致的,还是会资源跨域
  • 顺便自我人之下,域名和服务器之间的映射关系

2.开启自我提升,尝试自己手写axios

  • 选择合适的底层,我们利用ajax去封装
  • 观察axios的调用方式,尝试模拟实现,多试多查
  • 具体细节可以忽略,大致思路去模拟
  • 先猜想,再去看源码,对比实现

3.要在正确的认知阶段做正确的事

  • 初出茅庐:多看多cv,cv后一定要总结他人的方法,并尝试你自己写,哪怕最后实现不了,也没问题,尝试的经验才是最宝贵的

  • 小试牛刀:渐渐的你脑海里已经有模块,组件,封装的模糊概念了。并且你可以正确的查找和阅读各种api,这时候虽然很痛苦,但是一定多积累,多读api,多写demo,你已经可以完整简单组件的封装了,不积跬步无以至千里。

  • 略有小成:你已经可以carry住大多数简易需求,你可以用简单单一的代码和模式去写项目了,习惯是好东西,但是也有不好的方法,你需要渐渐的尝试新东西,新思想,新写法,逐步应用到你得项目里,学海无涯

  • 渐入佳境:这个阶段我理解的为思绪爆炸,就是目前你已经可以做一块砖了,哪里需要哪里搬了,但是你遇到的问题,已经不仅仅局限于,解决问题,而是想要往深处理解,甚至挖他的源码。这时候,将会是我们知识融合融汇的阶段,因为你去挖源码,看设计模式,看思路,会将你所有的经验串联起来,最后形成你得独特的见解,将来你得路将不仅仅局限于js,感觉自己已经摸到了一丝架构或者全栈的感觉了。

  • 学无止境:可以自己尝试拓展,node方向的知识,因为node可以平滑过渡,尝试自己node搭建服务器,自己配置Nginx,补充自己数据库知识,学无止境,等我们用node搭建一个服务后,我们将会站在后端的角度,审视我们的字段,审视接口存在必要性,审视接口的设计是否合理。