Next.js中併發時傳遞cookie錯亂的解決辦法
一、問題描述
最近在開發專案時遇到了一個問題,我們使用cookie儲存登入資訊,當用戶已經登入過我們的網站(儲存了cookie),再次進入網站後可能顯示別人的賬戶資訊,重新整理一下才能正確顯示自己的登入資訊。
經過折磨人的測試才重現了場景,發現是當兩人或以上同時訪問網站時,有一個人的cookie會把另一個人的cookie覆蓋,就導致了被覆蓋的這個人的賬戶資訊出了問題。
二、產生原因
問題出現在服務端請求中,在修改之前專案中的server.js檔案裡關於cookie是這樣寫的:
... server.get('*', (req, res) => { axios.defaults.headers.Cookie = req.headers.cookie||""; ... axios.post('/api/config/findSeoConfig',{'keyName':'seo_mall'}).then((response){ ... } ... } ...
首先在headers裡設定了一個預設Cookie,但是這裡Cookie是全域性的,下面發axios封裝過的post請求就會直接取這個全域性的Cookie,但是Next裡沒有執行緒鎖的功能,當併發操作時,它不會讓一個執行緒執行時另一個執行緒處於阻塞狀態,而是讓多個使用者的非同步請求同時執行,所以誰後進就會把先進的cookie給覆蓋掉,所以造成了混亂。
三、解決辦法
取消全域性Cookie,給服務端的每一個請求都單獨加入cookie
... server.get('*', (req, res) => { //axios.defaults.headers.Cookie = req.headers.cookie||""; axios.defaults.withCredentials = true;//設定跨域請求時需要使用憑證 ... axios({ headers: {cookie: req.headers.cookie}, url: '/api/config/findSeoConfig', method: 'post', data: {'keyName' : 'seo_mall'}, }).then((response) => { ... } ... } ...
Next的page中的getInitialProps()方法中發的dispatch也屬於服務端請求,也需要單獨新增cookie
...
static async getInitialProps(props) {
const {store, req} = props;
await store.dispatch({
type: 'mallTopAd/getMallTopAd',
payload: {
cookie: req.headers.cookie,
}
});
...
}
...
model層和servcie層就不多加贅述
//model層
...
effects: {
* getMallTopAd({payload}, {call, put}) {
...
}
...
}
...
//service層
...
export function fetchMallTopAd(params) {
return request({
url: mallTopAd,
method: 'post',
data: params,
})
}
...
service中的request是自己封裝的處理髮axios的方法,其實本質做法就是把發的axios請求的headers中加入Cookie。
//utils/request.js
...
const fetchData = (options) => {
let {
method = 'get',
data,
url,
} = options;
//如果cookie存在,執行下面switch的預設方法
if(data && data.cookie){
options.headers = {Cookie: data.cookie};
method = '';
delete data.cookie;//傳入後臺介面中的資料除去cookie
}
const cloneData = lodash.cloneDeep(data);
switch (method.toLowerCase()) {
case 'get':
return axios.get(url, {
params: cloneData,
})
case 'delete':
return axios.delete(url, {
data: cloneData,
})
case 'post':
return axios.post(url, cloneData)
case 'put':
return axios.put(url, cloneData)
case 'patch':
return axios.patch(url, cloneData)
default:
return axios(options)
}
}
export default function request (options) {
return fetchData(options).then((response) => {
...
}
}
經過這樣的處理,所有的服務端請求都會執行default中的axios,類似於上面寫的server.js中的處理。不直接在‘post’、‘get’等中直接新增headers是因為改動大比較麻煩,所以都放在default中處理了。headers中不僅能改Cookie,還能改Content-Type等,而且寫法也比較靈活,我這裡只是專案裡用的封裝的request才略顯笨重。坑還是要慢慢爬。
四、總結
總而言之言而總之,給每一個服務端請求加上Cookie就完事兒了。我們這個問題是個線上bug,被Next安排的明明白白。