前言
最近应用next.js开发过程中发现服务端set-cookie返回设置到浏览器不胜利,于是钻研了一下如何解决,分享给大家。
操作
前后台cookie如何互相传递?
1、前端如何传递cookie到后盾?
前端通过axios(或者fetch也能够)调用后盾接口的时候通过request申请头header的cookie属性(前端是你的浏览器中存在Cookie)带到后盾,前提是要同源,如:前端地址是:www.baidu.com
,后盾是:www.baiud.com/api
或者api.baidu.com
,这样的能力拜访浏览器中的cookie。
2、后盾如何传递cookie到前端?
后盾通过response申请头header的set-cookie属性带到前端浏览器,主动就能写到指定域名下。
理解next.js的执行过程
const pageA = (props) => { return <div> this is Page A</div>}export async function getServerSideProps(context) { const res = await axios({ url: "http://www.baidu.com/api/getUserList", data: xxx }); const data = res?.data; return { props: { data } }}export default pageA;
下面是一段非常简单的next.js页面的代码,它分为两局部,页面pageA和服务端获取接口数据getServerSideProps,当刷新页面或者首次关上页面时首先执行的是getServerSideProps办法,执行实现 之后才到pageA办法体中,是这样一个执行过程。
这也是SSR渲染最外围的中央,先从后盾返回数据再渲染出页面,缩小SPA解释js的等待时间。
留神:getServerSideProps
办法只有在页面第一次渲染的时候才执行(或者刷新页面、跳转页面),前面就不会再进来了。
next.js客户端如何传递cookie到后盾?
首先咱们看一个问题就是下面的http://www.baidu.com/api/getUserList
接口是没方法传递cookie到后盾的,为什么不能把cookie传递到后盾呢?怎么能力传递cookie到后盾呢?
首先答复第一个问题,因为getServerSideProps
执行在服务端所以是拿不到浏览器外面的cookie的,这时候须要通过next.js的context属性拿到。
答复第二个问题,只有通过上面的代码手动指定,这也是SSR非凡的中央。
axios.defaults.headers.cookie = context.req.headers.cookie || null
当然如果页面曾经渲染完,这时候你通过页面管制接口的拜访的时候就不必这么麻烦,因为浏览器会主动帮你把cookie带到接口的申请头:request header cookie上。
next.js服务端如何传递cookie到客户端浏览器?
曾经理解如何将cookie从客户端传递到服务端之后 ,咱们再来解决如何将cookie从服务端传递到客户端浏览器中,下面曾经讲过后盾是通过接口中返回的response申请头中的set-cookie属性传递过去的,如果是SPA那么间接就能够设置到Cookie中,然而咱们是SSR是next.js当然没那么简略了,那么咱们如何设置呢?
要做两步操作:
1、对axios返回申请头做设置
2、getServerSideProps
办法中再次设置set-cookie
const axiosInstance = axios.create({ baseURL: `http://www.baidu.com/api`, withCredentials: true,});axiosInstance.interceptors.response.use(function (response) { axiosInstance.defaults.headers.setCookie = response.headers['set-cookie'] return response;}, function (error) { // 超出 2xx 范畴的状态码都会触发该函数。 // 对响应谬误做点什么 return Promise.reject(error);});export default axiosInstance;
下面的axiosInstance.defaults.headers.setCookie = response.headers['set-cookie']
代码就是把后盾返回的set-cookie属性赋值给axiosInstance.defaults.headers.setCookie
,而后,回到getServerSideProps
办法中,再在最初返回给浏览器中,如下所示:
const pageA = (props) => { return <div> this is Page A</div>}export async function getServerSideProps(context) { // 1、获取cookie并保留到axios申请头cookie中 axios.defaults.headers.cookie = ctx.req.headers.cookie || null const res = await axios({ url: "http://www.baidu.com/api/getUserList", data: xxx }); const data = res?.data; // 2、判断申请头中是否有set-cookie,如果有,则保留并同步到浏览器中 if(axios.defaults.headers.setCookie){ ctx.res.setHeader('set-cookie', axios.defaults.headers.setCookie) delete axios.defaults.headers.setCookie } return { props: { data } }}export default pageA;
这样就实现了后盾set-cookie同步cookie到客户端Cookie中,然而,这里还有个问题,就是getServerSideProps
办法中如果你申请多于一个接口时,set-cookie只有最初一个起应用,什么意思呢?
const res1 = await axios({ url: "http://www.baidu.com/api/getUserList1", data: xxx });const res2 = await axios({ url: "http://www.baidu.com/api/getUserList2", data: xxx });const res3 = await axios({ url: "http://www.baidu.com/api/getUserList3", data: xxx });
下面三个办法执行之后,只有getUserList3
这个接口的set-cookie保留到客户端Cookie中,这是为什么呢?咱们再来看看这段代码:
axiosInstance.defaults.headers.setCookie = response.headers['set-cookie']
下面这段代码每次执行完之后axiosInstance.defaults.headers.setCookie
都会被response.headers['set-cookie']
间接笼罩了,所以当代码从getUserList1
执行到getUserList3
之后,set-cookie
就是最初一个办法的set-cookie
了。
看到下面的问题你是不是曾经想到了,对,就是合并把三个办法外面的set-cookie
合并到axiosInstance.defaults.headers.setCookie
中,所以咱们再来批改下代码:
// 增加响应拦截器axiosInstance.interceptors.response.use(function (response) { // 指标:合并setCookie // A、将response.headers['set-cookie']合并到axios.defaults.headers.setCookie中 // B、将axios.defaults.headers.setCookie合并到axios.defaults.headers.cookie中,目标是:每次申请axios申请头中的cookie都是最新的 // 留神:set-cookie格局和cookie格局区别 /** axios.defaults.headers.setCookie和response.headers['set-cookie']格局如下 * * axios.defaults.headers.setCookie = [ * 'name=Justin; Path=/; Max-Age=365; Expires=Mon, 15 Aug 2022 13:35:08 GMT; Secure; HttpOnly; SameSite=None' * ] * * **/ /** axios.defaults.headers.cookie 格局如下 * * axios.defaults.headers.cookie = name=Justin;age=18;sex=男 * * **/ // A1、判断是否是服务端,并且返回申请头中有set-cookie if(typeof window === 'undefined' && response.headers['set-cookie']){ // A2、判断axios.defaults.headers.setCookie是否是数组 // A2.1、如果是,则将response.headers['set-cookie']合并到axios.defaults.headers.setCookie // 留神:axios.defaults.headers.setCookie默认是undefined,而response.headers['set-cookie']默认是数组 if(Array.isArray(axiosInstance.defaults.headers.setCookie) ){ // A2.1.1、将后盾返回的set-cookie字符串和axios.defaults.headers.setCookie转化成对象数组 // 留神:response.headers['set-cookie']可能有多个,它是一个数组 /** setCookie.parse(response.headers['set-cookie'])和setCookie.parse(axios.defaults.headers.setCookie)格局如下 * setCookie.parse(response.headers['set-cookie']) = [ { name: 'userName', value: 'Justin', path: '/', maxAge: 365, expires: 2022-08-16T07:56:46.000Z, secure: true, httpOnly: true, sameSite: 'None' } ] * **/ const _resSetCookie = setCookie.parse(response.headers['set-cookie']) const _axiosSetCookie = setCookie.parse(axiosInstance.defaults.headers.setCookie) // A2.1.2、利用reduce,合并_resSetCookie和_axiosSetCookie对象到result中(有则替换,无则新增) const result = _resSetCookie.reduce((arr1, arr2)=>{ // arr1第一次进来是等于初始化化值:_axiosSetCookie // arr2顺次是_resSetCookie中的对象 let isFlag = false arr1.forEach(item => { if(item.name === arr2.name){ isFlag = true item = Object.assign(item, arr2) } }) if(!isFlag){ arr1.push(arr2) } // 返回后果值arr1,作为reduce下一次的数据 return arr1 }, _axiosSetCookie) let newSetCookie = [] result.forEach(item =>{ // 将cookie对象转换成cookie字符串 // newSetCookie = ['name=Justin; Path=/; Max-Age=365; Expires=Mon, 15 Aug 2022 13:35:08 GMT; Secure; HttpOnly; SameSite=None'] newSetCookie.push(cookie.serialize(item.name, item.value, item)) }) // A2.1.3、合并完之后,赋值给axios.defaults.headers.setCookie axiosInstance.defaults.headers.setCookie = newSetCookie }else{ // A2.2、如果否,则将response.headers['set-cookie']间接赋值 axiosInstance.defaults.headers.setCookie = response.headers['set-cookie'] } // B1、因为axios.defaults.headers.cookie不是最新的,所以要同步这样后续的申请的cookie都是最新的了 // B1.1、将axios.defaults.headers.setCookie转化成key:value对象数组 const _parseSetCookie = setCookie.parse(axiosInstance.defaults.headers.setCookie) // B1.2、将axios.defaults.headers.cookie字符串转化成key:value对象 /** cookie.parse(axiosInstance.defaults.headers.cookie)格局如下 * * { * userName: Justin, * age: 18, * sex: 男 * } * * **/ const _parseCookie = cookie.parse(axiosInstance.defaults.headers.cookie) // B1.3、将axios.defaults.headers.setCookie赋值给axios.defaults.headers.cookie(有则替换,无则新增) _parseSetCookie.forEach(cookie => { _parseCookie[cookie.name] = cookie.value }) // B1.4、将赋值后的key:value对象转换成key=value数组 // 转换成格局为:_resultCookie = ["userName=Justin", "age=19", "sex=男"] let _resultCookie = [] for (const key in _parseCookie) { _resultCookie.push(cookie.serialize(key, _parseCookie[key])) } // B1.5、将key=value的cookie数组转换成key=value;字符串赋值给axiosInstance.defaults.headers.cookie // 转换成格局为:axios.defaults.headers.cookie = "userName=Justin;age=19;sex=男" axiosInstance.defaults.headers.cookie = _resultCookie.join(';') } return response;}, function (error) { // 超出 2xx 范畴的状态码都会触发该函数。 // 对响应谬误做点什么 return Promise.reject(error);});export default axiosInstance;
有点多?不要怕,把正文去掉就一点点货色,大家能够通过正文看看下面代码如何实现的,当然你能够间接复制过来也行,下面代码实现两个指标:
A、将response.headers['set-cookie']合并到axios.defaults.headers.setCookie中
B、将axios.defaults.headers.setCookie合并到axios.defaults.headers.cookie中,目标是:每次申请axios申请头中的cookie都是最新的
最初,这样就实现了Cookie在客户端和服务端传递了。
最初我还想说一下,这种状况仅限呈现在第一次渲染页面时getServerSideProps
办法中遇到的问题,而当页面渲染实现之后,就不必这么麻烦了。
总结
1、如果想理解如何解决跨域问题的能够看这篇文章
2、next.js页面第一次渲染页面只有在getServerSideProps
办法中通过context获取和设置cookie。