mmf-blog-vue2-ssr icon indicating copy to clipboard operation
mmf-blog-vue2-ssr copied to clipboard

做服务端渲染时,只保存了一份cookie,那多个用户同时请求时好像会交叉请求状态污染

Open Kim09AI opened this issue 6 years ago • 4 comments

看了一下代码,先是在entry-server.js中调了api.setCookies(context.cookies),然后在this.api中就已经设置好cookie了,所有请求使用的都是同一份index-server.js导出的实例,这样在多个用户同时请求时,如A先请求,先设置了A的cookie,A的请求还没处理完成,B也来请求了,这样不是B的cookie会覆盖掉A的cookie吗,这样后续A的请求不是就会被污染了吗?如果分析有误,还请指正!

Kim09AI avatar Apr 27 '18 02:04 Kim09AI

确实是这样, 从vue2.3开始, runInNewContext 建议设置成 false 后, ssr 的cookies就是个问题, 服务端每个用户都共享一个上下文, 导致cookies不能存在服务端给api调用, 我这边也没什么好的思路, 目前可能把代码改回去, 还是得在asyncData, 把cookies传到api里去...

要是有什么好的想法, 也可以说说, 一起讨论下.

lincenying avatar Apr 27 '18 05:04 lincenying

刚想到一种方法,就是像创建app、router、store一样去创建axios,但是axios又没必要为每一个连接就axios.create()一个实例,那就共享一个axios.create()的实例就好了,只是为每一个连接创建一部分私有的东西,然后在里面共享同一个axios.create()出来的实例,通过export default CreateAxios 出来一个类,在创建store的时候传入store进行初始化CreateAxios,把store和CreateAxios的实例进行绑定,这样通过CreateAxios实例去发起请求就可以拿到对应连接的store中的数据了,文章写的太烂了,还是看代码

以下代码是在nuxt.js中的实现

一、axios封装,导出一个CreateAxios

// ./utils/index.js
import originAxios from 'axios'
import qs from 'qs'
import Api from '../api'

const axios = originAxios.create({
    baseURL: 'https://cnodejs.org/api/v1',
    headers: {
        post: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    }
})

// 响应拦截器
axios.interceptors.response.use(response => {
    return response && response.data
}, err => {
    // 错误处理, 处理success为false的情况
    if (err.response && err.response.data) {
        return Promise.resolve(err.response.data)
    }
    return Promise.reject(err)
})

class CreateAxios extends Api {
    constructor(store) {
        super(store)
        this.store = store
    }

    getAccessToken() {
        return this.store.state.accessToken
    }

    get(url, config = {}) {
        let accessToken = this.getAccessToken()

        config.params = config.params || {}
        // 变量是accessToken 做参数时全小写accesstoken
        accessToken && (config.params.accesstoken = accessToken)

        return axios.get(url, config)
    }

    post(url, data = {}, config = {}) {
        let accessToken = this.getAccessToken()

        accessToken && (data.accesstoken = accessToken)

        return axios.post(url, qs.stringify(data), config)
    }

    // 实例保存在store.$axios上,返回服务端渲染结果时会用JSON.stringify对store处理
    // 添加toJSON绕过JSON.stringify
    toJSON() {}
}

export default CreateAxios

至于extends的Api只是我封装的一些请求

// ./api/index.js
class Api {
    get() {
        throw new Error('Abstract methods must be implemented')
    }

    post() {
        throw new Error('Abstract methods must be implemented')
    }

    // 获取主题列表
    getTopics(page = 1, tab = 'all', limit = 40, mdrender = 'false') {
        return this.get('/topics', {
            params: {
                page,
                tab,
                limit,
                mdrender
            }
        })
    }
    
    // ...
}

export default Api

二、在store工厂函数中创建CreateAxios的实例,传入store进行绑定

// ./store/index.js
import CreateAxios from '../utils/axios'

Vue.use(Vuex)

const store = () => {
    let store = new Vuex.Store({
        state,
        getters,
        mutations,
        actions
    })
    
    store.$axios = store.state.$axios =  new CreateAxios(store)

    return store
}

export default store

这样在创建store的同时,就和CreateAxios的实例绑定好了,在组件内就可以通过store.$axios或this.$store.$axios去发起请求,在action中可以通过state.$axios或rootState.axios发起请求(仅限服务端渲染时,因为在客户端store.replaceState(window.INITIAL_STATE)把state.$axios给覆盖了),暂时还没有好的方法, 为了在组件内调用方便些,可以通过this.$axios调用,就用这样一个全局mixin去处理了,不过也遇到一些问题,原因未明,不过在页面级的组件里面都是可以访问到的,从beforeCreate就可以

// ./mixin/index.js
export const axiosMixin = {
    beforeCreate() {
        // 原因未知,前面有几个组件beforeCreate的this.$store为undefined
        if (this.$store && this.$store.$axios) {
            this.$axios = this.$store.$axios
        }
    }
}

三、cookie的共享,在store的nuxtServerInit(只会在服务端渲染时执行)中获取cookie,把cookie中的accessToken保存到store,然后CreateAxios的实例就可以通过store拿到对应的accessToken了

// ./store/actions.js
export const nuxtServerInit = async ({ commit, dispatch, state }, { req }) => {
    let accessToken = parseCookieByName(req.headers.cookie, 'access_token')

    if (!!accessToken) {
        try {
            let res = await state.$axios.checkAccesstoken(accessToken)

            if (res.success) {
                let userDetail = await state.$axios.getUserDetail(res.loginname)
                userDetail.data.id = res.id

                // 提交登录状态及用户信息
                dispatch('setUserInfo', {
                    loginState: true,
                    user: userDetail.data,
                    accessToken: accessToken
                })
            }
        } catch (e) {
            console.log('fail in nuxtServerInit', e.message)
        }
    }
}

总结

对比前后,axios请求由之前可以在任何地方发起,变成只能在asyncData、组件内及action中(服务端渲染时)请求,因为是基于nuxt的项目,好像有些地方没有vue ssr好定制,不过cookie的问题好像可以解决了,还有很多不足,以及可以如何优化和改进,也不知道有没有什么潜在的问题,还请多指点了,要是有其他好的方法,欢迎分享! github地址 线上地址

Kim09AI avatar Apr 27 '18 14:04 Kim09AI

应该这样就可以在客户端,在action中使用state.$axios或rootState.$axios发请求了

if (process.browser) {
    let replaceState = store.replaceState.bind(store)
    store.replaceState = (...args) => {
        replaceState(...args)
        store.state.$axios = store.$axios
        replaceState = null
    }
}

Kim09AI avatar Apr 27 '18 15:04 Kim09AI

昨天测试了下, 应该可行...注入store应该是最好的方法了...

lincenying avatar Apr 28 '18 01:04 lincenying