问题出现的场景
因为最近在做一个小程序的项目,在建立前后端连接的过程中,发现了一个非常让人奇怪的现象:本身小程序是通过调用wx.https()方法来发起http请求的,但是你会发现,如果你在后端将值保存到了request或者session中,这个值你再次调用的时候就不见了!取值的时候会出现NullPointerException,或者你在使用了Spring Security、Shiro这样的权限校验框架以后,会发现登录后出现了权限丢失的问题。
这到底是为什么呢?根据我的经验,我怀疑是session发生了变化,为了证明这一点,我通过观察两次请求的session是否为同一个得到了最终的结论。
小程序发起请求的代码是这样的:
wx.request({
url: serverUrl + '/login',
data: {
username: username,
password: password
},
header: {
'content-type': 'application/json' // 默认值
},
success:function(){
console.log("登录成功");
}
})
通过观察小程序的调试器的network,发现果然两次请求的session发生了变化。我猜想是因为小程序没有保存连接的Cookie,果然,通过查阅资料,发现小程序是无法建立持久化的连接的,所以就不会主动保存Cookie。其实这也在一定程度上体现了小程序用完即走的特性。
那么解决的办法也就很清楚了:将首次请求返回的response中的Cookie保存下来,然后下次发送请求时将此值放到请求头中。
解决问题的方案
实际上,我们在使用浏览器访问某个网站的时候,浏览器会主动的将Cookie中的JSESSIONID存入浏览器的Cookie缓存中,这样下次再请求的时候,就会自动将这个JESSIONID加到请求头中,这样对方的服务器就能够识别我们,从而达到建立一种“持久化”连接的状态。实际上我们知道,这种连接并不是真正持久的,都是需要数据的时候再次建立连接,然后断开。整个过程如图所示:
方案一:将JSESSIONID放入全局变量
其实最简单的办法就是,在第一次请求完成后,就将JSESSIONID放到全局的SESSIONID中,然后每次请求的时候在header中这样写:
header: {
'content-type': 'application/json', // 默认
'Cookie': app.globalData.JSESSIONID
},
这就能够做到SESSION不丢失,但是这样很麻烦,如果后期代码编写不当,这数据很容易丢失,所以我们并不推荐使用这种方法。
方案二:将JESSIONID放入缓存
微信为我们提供了几个方法,这几个方法类似于Java中的session.setAttrabiute()方法,都是
wx.request({
url: serverUrl + '/login',
data: {
},
header: {
'content-type': 'application/json' // 默认值
},
success:function(res){
//取出session
var cookie = res.header["Set-Cookie"];
if (undefined != cookie) {
var sessionPos;
var rememberMe;
if ((sessionPos = cookie.indexOf("JSESSIONID=")) != -1) {
//每次请求成功都将sessionId放入缓存
wx.setStorageSync("JSESSIONID", cookie.substring(sessionPos, 48));
}
if ((sessionPos = cookie.indexOf("rememberMe=")) != -1) {
//设置rememberme
wx.setStorageSync("rememberMe", cookie.substring(sessionPos + 78, 712));
}
}
}
})
这段代码做的就是从Cookie的字符串中取到SessionId和RememberMe,可能因为版本不同等问题造成长度不一致,这个自己计算一下就行了。
然后就可以在小程序中建立“持久化的连接”了。
建个Util
其实像上面那样做可以,但是如果遇到session失效的情况,原有的写入的session就没了,还有就是代码复用的问题,像上面那样做,可能你每次请求的时候都要自己写Cookie之类的代码,这就很麻烦了,最好的办法就是建一个工具类,其中包括持久化连接的代码。
/**
* 公共微信https请求封装
* @param url
* @param type
* @param data
* @param callBack 回调函数
*/
function https(url, type, data, callBack, header) {
if (!data.isHideLoad) {
wx.showLoading({
title: '加载中',
})
}
wx.showNavigationBarLoading();
wx.request({
url: url,
method: type,
data: data,
header: header ? header : ({
"Content-Type": "application/json",
"Cookie": wx.getStorageSync('JSESSIONID') + wx.getStorageSync('rememberMe')
}),
success: function(res) {
//取出session
var cookie = res.header["Set-Cookie"];
if (undefined != cookie) {
var sessionPos;
var rememberMe;
if ((sessionPos = cookie.indexOf("JSESSIONID=")) != -1) {
//每次请求成功都将sessionId放入缓存
wx.setStorageSync("JSESSIONID", cookie.substring(sessionPos, 48));
}
if ((sessionPos = cookie.indexOf("rememberMe=")) != -1) {
//设置rememberme
wx.setStorageSync("rememberMe", cookie.substring(sessionPos + 78, 712));
}
}
callBack(res.data);
},
fail: function(error) {
// showToast("登录过期,请重新登录", "none");
wx.reLaunch({
url: '../../account/login',
success: function() {
wx.showToast({
title: '登录过期,请重新登录',
icon: 'none'
})
}
})
},
complete: function(res) {
if (res.status === 400) {
showToast("家校通请求未授权");
}
wx.hideLoading();
wx.stopPullDownRefresh();
wx.hideNavigationBarLoading();
}
})
}
需要用到请求的时候,就先导入util,然后调用util.https(params…)。
如果你发现无法调用这个方法,那么问题是你没有将https这个方法暴露出去,在util方法的最后,写上这样一段代码:
module.exports = {
https: https
}