HTTP协议的无状态特性(对业务处理没有记忆能力)导致如果后续处理需要前面的信息,则它必须重传前面信息。这也导致每次链接传送的数据量增大。为了克服HTTP协议的这个缺点,出现了两种用于保持HTTP链接状态的技术Cookie和Session。
一. Cookie
Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。
Cookie是由服务器端生成,发送给User-Agent(一般是web浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用Cookie)。Cookie名称和值可以由服务器端开发自己定义,对于JSP而言也可以直接写入Sessionid,这样服务器可以知道该用户是否合法用户以及是否需要重新登录等。
Cookie的用途
Cookies最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookies的功用。另一个重要应用场合是“购物车”之类处理。用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookies,以便在最后付款时提取信息。
Cookie生存周期
Cookie可以保持登录信息到用户下次与服务器的会话,换句话说,下次访问同一网站时,用户会发现不必输入用户名和密码就已经登录了(当然,不排除用户手工删除Cookie)。而还有一些Cookie在用户退出会话的时候就被删除了,这样可以有效保护个人隐私。
Cookie在生成时就会被指定一个Expire值,这就是Cookie的生存周期,在这个周期内Cookie有效,超出周期Cookie就会被清除。有些页面将Cookie的生存周期设置为“0”或负值,这样在关闭页面时,就马上清除Cookie,不会记录用户信息,更加安全。
二.Session
Session是指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间以及如果需要的话,可能还有一定的操作空间。Session实际上是一个特定的时间概念。
Session的生成
当客户端访问服务器时,服务器根据需求设置Session,将会话信息保存在服务器上,同时将标示Session的SessionId传递给客户端浏览器,浏览器将这个SessionId保存在内存中,我们称之为无过期时间的Cookie。浏览器关闭后,这个Cookie就会被清掉,它不会存在于用户的Cookie临时文件。以后浏览器每次请求都会额外加上这个参数值,服务器会根据这个SessionId,就能取得客户端的数据信息。
Session的创建流程
当用浏览器登录到某网站服务器时,先找对应的Cookie文件,当首次访问是当然没有Cookie文件,所以在请求头部中没有Cookie的内容,即在请求头部中没有类似Cookie: JSESSIONID=XXXXXXXXXXXXXXX的内容,这时当请求到达服务器后,服务器看请求头中没有JSESSIONID值,于是生成一个Session对象,并由某种算法产生一个值赋给这个Session的id,并将SessionId,和Session对象放入HashMap中,然后将这个SessionId发回以客户端,即在响应的头部有一行:Set-Cookie:JSESSIONID=XXXXXXXXXXXXXX,浏览器解析后会将在JSESSIONID和值记录到Cookie中。
Session的作用
当用浏览器再次请求此网站的另一页面时,浏览器检查看有没有Cookie,这时有Cookie(前提是Cookie没有过期),会自动的把Cookie的内容附加到请求头中,即在请求头附加了一行:Cookie:JSESSIONID=XXXXXXXXXXXXXXXXXXXXXXX,服务器接收到请求后会根据JSESSIONID的值知道SessionId的值,在tomcat中两个值是一样的,然后你可根据这个SessionId找到对应的Session,可由Session中登录后设定的某些值是否为空,来判断此用户是否登录。当用户安全登出后(调用session.invalidate()),服务器会将Session销毁,并生成一个新的JSESSIONID发回用客户端,浏览器接收响应后,会将Cookie中的JSESSIONID换成新值,当用户再次访问此服务器时,会在请求中自动的加入新的JSESSIONID的值发到服务器,这时服务器根据JSESSIONID找不对应的Session了,因此就可知道是一次新的会话,服务器会生成一个Session对象,并将客户端发过来的JSESSION的值赋给这个Session的id。如果客户端禁用了Cookie那么服务器可就无法将session内容与客户端对应上了。
redis数据库
redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库。
redis是一种高级的key:value存储系统,其中value支持五种数据类型:
1.字符串(strings)
2.字符串列表(lists)
3.字符串集合(sets)
4.有序字符串集合(sorted sets)
5.哈希(hashes)
鉴于其实内存型数据库的优势常做缓存数据库保存每个客户端的Session信息。
客户端第一次登陆实例分析
客户端第一次登陆时服务器Selvet的处理(java代码)
public JsonObject process(RequestParam param) throws Exception {
//param为客户端发送的字符串形式的信息
String userName = param.getValue("user_name");//从RequestParam参数中读取用户名;
String pwd = param.getValue("pwd");//从RequestParam参数中读取用户密码
Assert.notNull(userName, "user_name can't be null!");
Assert.notNull(pwd, "Pwd can't be null!");
TUser user = UserBusiness.queryUserByUserName(userName);//从mysql数据库中读取用户信息包括用户名和用户密码;
if (user == null) {
return respError("user not exist!");
}
if (!user.getPassword().equalsIgnoreCase(pwd)) {
return respError("password wrong!");
}//判断用户密码是否正确;
JsonObject jsonObject = respRightOboxs(user.getUserId());//返回给客户端的一些用户请求信息;
jsonObject.addProperty(ACCESS_TOKEN, AccessTokenTool.create(user.getUserId()));//此处即为服务器为客户端返回的SessionID(由AccessTokenTool.create()函数返回);
return jsonObject;
}
下面咱们来看下AccessTokenTool.create()函数的实现
public static String create(int userId) {
String uuid = RandomUtils.genUUID();
RedisCache.getJedisCache().setex(uuid, defalut_time, String.valueOf(userId));
return uuid;
}
该函数主要调用的是下面这个函数即把信息存储到redis
public boolean setex(String key, int seconds, String value) {
boolean result = false;
ShardedJedis sJedis = null;
try {
sJedis = borrowResource();
String status = sJedis.setex(key, seconds, value);
if ("OK".equalsIgnoreCase(status)) {
result = true;
}
returnResource(sJedis);
} catch (Exception e) {
returnBrokenResource(sJedis);
LogService.error("JedisCache.setex falid", e);
}
return result;
}
我们把目光集中在public boolean setex(String key, int seconds, String value)函数上,其中传入的由随机函数RandomUtils.genUUID()产生的UUID作为Key值,自定义的defalut_time有效时间以及用户ID userid作为value存储到redis服务器上。setex函数是封装jedis(java操作redis服务器的第三方库)函数。至此当前登陆用户的信息被以键值对的形式保存到redis服务器即UUID/userid;其中ACCESS_TOKEN与UUID也以键值对的形式保存到JSON对象中,并通过jsonObject 发送到客户端保存起来。当客户端在没有断开连接的情况下,第二次发送请求数据包时会自动把ACCESS_TOKEN封装在数据包中并由服务器中对应的selvet解析出ACCESS_TOKEN中的UUID并把UUID作为Key值从redis数据库中取出对应的value,即Session并比较查看是否是该用户。
从redis中取Session的函数
public byte[] get(byte[] key) {
byte[] result = null;
ShardedJedis sJedis = null;
try {
sJedis = borrowResource();
result = sJedis.get(key);
returnResource(sJedis);
} catch (Exception e) {
returnBrokenResource(sJedis);
LogService.error("JedisCache.get falid", e);
}
return result;
}