好久没研究Web通信内容了,回顾一下
1.Cookie引入
当用户在未登录状态下访问京东
网站,向购物车中添加某些商品后,将浏览器关闭,然后再打开浏览器访问“京东”,此时查看购物车会发现,购物车中仍然具有刚才添加的商
品。那么这些商品信息是保存在哪里的呢?
当在登录 126 邮箱时选上“十天内免登录”,再登录时的确是不需要输入用户名与密码
了,那么是网站不需要对用户进行验证了?当然不是。那么,网站用于用户验证的用户名与密码是从哪里获取到的?
这两个例子都有一个共同点:换台机器就不再是前面所述情况了。即换台电脑打开“京
东”购物网站,购物车中就没有原来选好的商品了。换台电脑打开 126 网站,仍然需要输入用户名与密码。那就说明一个问题:那些信息是保存在客户端电脑里的,并没有保存在服务器中。将原来的电脑重启后,再次访问这些网站,发现购物车中仍有原来的商品,126 网站仍然不需要输入用户名与密码。那就说明一个问题:那些信息是保存在客户端电脑的硬盘中的,而不是内存中。客户端电脑中用于保存这些会话状态的资源,称为 cookie
。
2.Cookie简介
Cookie 是 1993 年由网景公司(Netscape)前雇员发明的一种进行网络会话状态跟踪的技术。会话是由一组请求与响应组成,是围绕着一件相关事情所进行的请求与响应。所以这些请求与响应之间一定是需要有数据传递的,即是需要进行会话状态跟踪的。然而 HTTP 协议是一种无状态协议,在不同的请求间是无法进行数据传递的。此时就需要一种可以进行请求间数据传递的会话跟踪技术,而 Cookie 就是一种这样的技术。
Cookie 是由服务器生成,保存在客户端的一种信息载体。这个载体中存放着用户访问该站点的会话状态信息。只要 Cookie 没有被清空,或都 Cookie 没有失效,那么,保存在其中的会话状态就有效。用户在提交第一次请求后,由服务器生成 Cookie,并将其封装到响应头中,以响应的形式发送给客户端。客户端接收到这个响应后,将 Cookie 保存到客户端。当客户端再次发送同类请求后,在请求中会携带保存在客户端的 Cookie 数据,发送到服务端,由服务器对会话进行跟踪。Cookie 技术并不是JavaWeb 开发专属技术,而是属于 Web 开发的技术,是所有 Web 开发语言均支持的技术。Cookie 是由若干键值对构成,这里的键一般称为 name,值称为 value。Cookie 中的键值对均为字符串。
在不同的浏览器下,其Cookie的保存位置及其查看方式都是不一样的,删除了某一浏览器下的Cookie,不会影响到其它浏览器中的Cookie
3.JAVAWEB中的Cookie
在JavaEE中的javax.servlet.http包中存在一个类Cookie,就是用于完成会话跟踪的Cookie。
其只有一个带参构造器:
public Cookie(String name, String value)
而 HttpServletResponse 中有一个方法,可以将 Cookie 添加到响应头中。
public void addCookie(Cookie cookie)
注意,一个 response 中可以添加多个 Cookie 的键值对。
(1)默认绑定路径
访问路径由资源路径与资源名称构成。默认情况下,Cookie 与访问路径中的资源路径绑定。只要用户发出带有绑定资源路径的请求,则在请求头部,将自动会携带与之绑定的 Cookie数据。,由于现在开发环境基本都是使用springboot框架
,但是其底层原理还是基于Servlet
那一套,因此这里就使用原始的Servlet
进行代码描述,方便更直观看懂:
A、定义Servlet
B.注册Servlet
C运行结果:
在 IE 或火狐浏览器的地址栏中输入 http://127.0.0.1:8080/cookie/test/some 访问路径,
打开 HttpWatch 工具,可以监测查看到,在响应头部,携带有 SomeServlet 中设置的 Cookie。
此时的两个 Cookie 默认与资源路径 http://127.0.0.1:8080/cookie/test 进行了绑定。当在浏览器地址栏中再次输入包含以上资源路径的请求地址后,例如,http://127.0.0.1:8080/cookie/test/xxx/ooo/jjj,一旦发出请求,则通过 HttpWatch 可查看到,在请求头部已经包含了刚才由服务端发送来的Cookie 数据。
(2)设置绑定路径
在 Cookie 类中,有一个方法,专门用于设置与 Cookie 对象绑定的路径。
public void setPath(String uri)
此时,当浏览器发出 http://127.0.0.1:8080/cookie/test/some 请求后会将 Cookie
放入到响应头部,并发送给客户端。但,不会将 Cookie 与该访问路径的资源路径进行绑定,而是将Cookie
与代码中指定的路径进行了绑定。
通过 HttpWatch 可以查看到响应头部信息中已经包含了与 Cookie
绑定了路径
所以,此时浏览器再发出包含 http://127.0.0.1:8080/cookie/test 资源路径的请求时,是
不会携带 Cookie 数据的。只有当发出包含 http://127.0.0.1:8080/cookie/abc 资源路径的请求时,在请求头部才会携带 Cookie 数据。
(3)将 Cookie 保存到硬盘
默认情况下,Cookie 是保存在浏览器的缓存中的,浏览器关闭,缓存消失,Cookie 消失。通过设置 Cookie 的有效时长,可以将 Cookie 写入到客户端硬盘文件中。
Cookie 类中有一个方法可以设置 Cookie 的有效时长。
public void setMaxAge(int expiry) 形参 expiry 的单位为秒,整型。若其值大于 0,则表示要将 Cookie 写入到硬盘文件中;小于 0,则表示 Cookie 存放在浏览器缓存中,与不设置时长等效;等于 0,则表示 Cookie产生后直接失效。
在前面项目的基础上直接修改 SomeServlet 即可。
查看响应头部信息中的 Cookie,发现其已经添加上了有效时长。
打开火狐浏览器,查看 Cookie,发现 Cookie 的过期时间已经不再是“在会话结束时”,
而是一个固定时间,那么,就说明其已经写入到了硬盘。
(4)服务端读取请求中的 Cookie
在 HttpServletRequest 中有一个方法,专门用于读取请求中所携带的 Cookie 数据。
public Cookie[] getCookies()
A、修改 SomeServlet
将 Cookie 与默认的资源路径绑定。
B.定义OtherServlet
C、 注册 OtherServlet
D、访问运行
先在 IE 或火狐浏览器的地址栏中输入 http://127.0.0.1:8080/cookie/test/some 访问路径,此时会将 Cookie 写入到浏览器客户端的硬盘中。
再在 IE 或火狐浏览器的地址栏中输入 http://127.0.0.1:8080/cookie/test/other 访问路径,此时会将硬盘中的 Cookie 数据通过请求发送给服务端。
而这样就实现了在两次请求中的会话跟踪。
4.Cookie 的禁用
浏览器是可以禁用 Cookie 的。所谓禁用 Cookie 是指客户端浏览器不接收服务器发送来的 Cookie。不过,现在的很多网站,若浏览器禁用了 Cookie,则将无法访问。例如,126 邮箱就要求浏览器不能禁用 Cookie。火狐浏览器中对于 Cookie 的禁用,是在火狐浏览器的“打开菜单/选项”的“隐私”标签中。不勾选“接受来自站点的 Cookie”,则表示禁用 Cookie。
5.HttpSession
Session
,即会话,是 Web 开发中的一种会话状态跟踪技术。当然,前面所讲述的 Cookie
也是一种会话跟踪技术。不同的是 Cookie
是将会话状态保存在了客户端,而 Session
则是将会话状态保存在了服务器端。那么,到底什么是“会话”?当用户打开浏览器,从发出第一次请求开始,一直到最终关闭浏览器,就表示一次会话的完成。
Session
并不是 JavaWeb
开发所特有的,而是整个 Web
开发中所使用的技术。在 JavaWeb
开发中,Session
是以 javax.servlet.http.HttpSession
的接口对象的形式出现的。
(1)Session对象的创建
若要对 Session 进行操作,则可以通过 HttpServletRequest 的 getSession()方法获取。该方法具有两个重载的方法。
public HttpSession getSession(boolean create)
public HttpSession getSession()
何时使用 getSession(true),即 getSession(),何时使用 getSession(false)呢?
一般情况下,若要向 Session 中存放数据, 则使用 getSession(true),即 getSession()。意义为:若当前存在 Session,则使用当前的 Session;若当前不存在 Session,则创建一个新的Session。因为存放数据是必须要有 Session 的。
若要从 Session 中获取数据,则一般使用 getSession(false)。意义为:若当前存在 Session,则从中获取数据;若当前根本就没有 Session,那就更不可能存在 Session 中的数据了。因为你要查找的数据只可能出现在已经存在的 Session 中,而不可能存在于新建的 Session 中。
(2)对 Session 域属性空间的操作
Session
是一个专门用于存放数据的集合,我们一般称这个用于存放数据的内存空间为域属性空间,简称域。HttpSession 中具有三个方法,是专门用于对该域属性空间中数据进行写、读操作的。
setAttribute
(String name, Object value)getAttribute
(String name)removeAttribute
(String name)(3)程序举例
项目:httpSession。在项目 cookie 上进行修改。
A修改 SomeServlet
向 Session 中存放数据。
B、 修改 OtherServlet
从 Session 中读取数据。注意,这里对于 Session 的获取方式是,使用 getSession(false)
获取。即存在 Session,则获取 Session,没有 Session,则返回为 null。
若在 OtherServlet 中添加 removeAttribute()方法,则 getAttribute()读取到的属性值将为null。
C、 访问运行
先让一个用户在一台电脑的浏览器中进行如下访问:
首先在地址栏中输入如下访问地址:
http://127.0.0.1:8080/httpSession/test/some?pname=zhangsan
再在地址栏中输入如下访问地址:
http://127.0.0.1:8080/httpSession/test/other
再让另一个用户在另一台电脑的浏览器中进行相同方式的访问。只不过输入的 pname
值不同。
D、结果分析
多个用户均在自己的电脑上访问当前应用,发现不同用户从 Session 中读取到的都是自
己所提交的参数值,并没有读取到别人的参数,并没有发生“错乱”现象。这是为什么呢?
Web开发中的Session机制,为每个用户都分配了一个Session。即一个用户一个Session,确切地说,是一次会话一个 Session 对象。同一用户可以发出多个会话,即会产生多个 Session。
Session 的工作原理(面试重点)
在服务器中系统会为每个会话维护一个 Session。不同的会话,对应不同的 Session。那么,系统是如何识别各个 Session 对象的?即是如何做到在同一会话过程中,一直使用的是同一个 Session 对象呢?
(1)写入 Session 列表
服务器对当前应用中的 Session 是以 Map 的形式进行管理的,这个 Map 称为 Session 列 表。该 Map 的 key 为一个 32 位长度的随机串,这个随机串称为 JSessionID,value 则为 Session对象的引用。当用户第一次提交请求时,服务端 Servlet 中执行到 request.getSession()方法后,会自动生成一个 Map.Entry 对象,key 为一个根据某种算法新生成的 JSessionID,value 则为新创建的 HttpSession 对象。
(2) 服务器生成并发送 Cookie
在将 Session 信息写入 Session 列表后,系统还会自动将“JSESSIONID”作为 name,这 个 32 位长度的随机串作为 value,以 Cookie 的形式存放到响应报头中,并随着响应,将该Cookie 发送到客户端。
(3) 客户端接收并发送 Cookie
客户端接收到这个 Cookie 后会将其存放到浏览器的缓存中。即,只要客户端浏览器不
关闭,浏览器缓存中的 Cookie 就不会消失。当用户提交第二次请求时,会将缓存中的这个 Cookie,伴随着请求的头部信息,一块发送到服务端。
(4) 从 Session 列表中查找
服务端从请求中读取到客户端发送来的 Cookie,并根据 Cookie 的 JSSESSIONID 的值,从Map 中查找相应 key 所对应的 value,即 Session 对象。然后,对该 Session 对象的域属性进行读写操作。
Session 的失效
Web 开发中引入的 Session 超时的概念,Session 的失效就是指 Session 的超时。若某个Session 在指定的时间范围内一直未被访问,那么 Session 将超时,即将失效。
在 web.xml 中可以通过标签设置 Session 的超时时间,单位为分钟。默认 Session 的超时时间为 30 分钟。需要再次强调的是,这个时间并不是从 Session 被创建开始计时的生命周期时长,而是从最后一次被访问开始计时,在指定的时长内一直未被访问的时长。
若未到超时时限,也可通过代码提前使 Session 失效。HttpSession 中的方法 Invalide(),
使得 Session 失效。
public void invalidate()
举例:
直接在项目 httpSession 中进行修改。
定义并注册 ThirdSession。
而对于项目的测试访问,可以遵循如下步骤:
Cookie 禁用后使用 Session 进行会话跟踪
从前面 Session 的工作原理可知,服务器只所以可以针对不同的会话找到不同的 Session,是因为 Cookie 完成了会话的跟踪。但是,若客户端浏览器将 Cookie 禁用,那么服务器还怎样保证同一会话使用的是同一个 Session 呢? 若客户端浏览器禁用了 Cookie,会发现向服务器所提交的每一次请求,服务器在给出的响应中都会包含名称为 JSESSIONID 的 Cookie,只不过这个 Cookie 的值每一次都不同。也就是说,只要客户端浏览器所提交的请求中没有包含 JSESSIONID,服务器就会认为这是一次新的会话的开始,就会为其生成一个 Map.Entry 对象,key 为新的 32 位长度的随机串,value
为新创建的 Session 会话引用。这样的话,也就无法实现会话跟踪了。
(1) 问题演示
项目:httpSession-closeCookie。在项目 httpSession 的基础上进行修改。
A、禁用 Cookie
首先将火狐浏览器的 Cookie 禁用。
C、 测试运行
在浏览器上运行该项目,每刷新一次页面,通过 HttpWatch 工具可以发现响应体中都会
有新的 JSESSIONID 生成。
再查看控制台的输出,会发现每次使用的 Session 都是不同的 Session。
为什么用户每刷新一次页面,服务器就会为其新生成一个 Session 呢?当服务器接收到
客户端发送来的请求后,会从请求中读取 Cookie。若没有找到名称为 JSESSIONID 的 Cookie,则说明这是一次新的请求,服务器就会为新请求生成一个新的 Session,并将新的 JSSEIONID保存到 Cookie 中发送给客户端。
(2) 手工重写 URL
在浏览器的 Cookie 被禁用的情况下,若要实现会话跟踪,需要使用 URL 重写机制。所
谓 URL 重写机制是指,将 JSessionID 添加到请求的最后。
A、运行方式
这里使用手工重写 URL 的方式来实现。将 JSessionID 添加到地址栏的请求最后。key 为全小写的 jsessionid,而 value 则为服务端响应头部发送来的 JSESSIONID。不过,需要的是原请求与 jsessionid 间使用分号分隔,而非问号。上面的程序在运行时,从响应头部将 JSessionID 复制,然后按照如下形式添加到请求中:
http://127.0.0.1:8080/httpSession-closeCookie/test/some;jsessionid=42F374„„16D6D
查看控制台,发现使用的 Session 对象是同一个 Session。
B、 运行结论
从前面的运行情况可知,客户端浏览器的关闭,并不会使得与之绑定的 Session 对象失
效。对一次请求的响应结束后,客户端浏览器与服务器间的连接就关闭了。所以浏览器的关闭,对于服务器来说,是不知情的。学习到这里,我们再来看什么是一次“会话”。从表面上看,一次会话就是指从浏览器打开并发送第一次对该服务器的请求开始,到浏览器关闭,这就是一次会话。但从本质上说,一次会话是指,从 Session 对象的创建开始,一直到最终的 Session 失效,这才是一次会话。即浏览器关闭后,对系统来说会话并未结束。只不过对于用户来说,好像会话结束了。
(3) 重定向的 URL 重写
HttpServletResponse 具有一个方法 encodeRedirectURL(),可以完成对重定向 URL 的重写,即在重定向的路径后会自动添加 jsessionid。
项目:httpSession-encodeurl。在项目 httpSession 基础上修改。
C、 运行分析
若在 SomeServlet 中对重定向的 URL 不进行重写,则跳转到 OtherServlet 后将无法从
Session 中读取数据。但,若重写了 URL,则可以读取的到,并且,浏览器地址栏中也可以看到被重写的 URL。
(4) 超链接的 URL 重写
HttpServletResponse 具有一个方法 encodeURL(),可以完成对类似对超链接这样的非重定向页面跳转的 URL 的重写,即在其路径后会自动添加 jsessionid。
项目:httpSession-encodeurl-2。在项目 httpSession-encodeurl 基础上修改。
若在 SomeServlet 中对于超链接的 URL 不进行重写,则跳转到 OtherServlet 后将无法从Session 中读取数据。但,若重写了 URL,则可以读取的到,并且,浏览器地址栏中也可以看到被重写的 URL。
域属性空间范围对比
在 JavaWeb 编程的 API 中,存在三个可以存放域属性的空间范围对象,这三个对象中所存储的域属性作用范围,由大到小分别为:
ServletContext,即 application,置入其中的域属性是整个应用范围的,可以完成跨会话共享数据。
HttpSession,置入其中的域属性是会话范围的,可以完成跨请求共享数据。
HttpServletRequest,置入其中的域属性是请求范围的,可以完成跨 Servlet 共享数据。但这些 Servlet 必须在同一请求中。对于这三个域属性空间对象的使用原则是,在可以保证功能需求的前提下,优先使用小范围的。这样不仅可以节省服务器内存,还可以保证数据的安全性