乐趣区

关于next.js:Nextjs-服务端操作Cookie

前言

最近应用 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。

退出移动版