Servlet(Server Applet),全称 Java Servlet,是用 Java 编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。本系列将一步步地写出一个 Servlet 程序。
这篇博文将演示如何使用 cookie
和 session
进行会话追踪。
HTTP 协议是一个无状态的协议,也就是说,在服务器眼中,每一个 HTTP 请求都是一个全新的请求,每个请求之间没有关联。所以我们需要一个可以管理请求中携带的用户信息的方法。而会话追踪就是一个可以管理用户信息的方法。
会话追踪可以通过下列几个方式实现:
- Cookie
- 表单隐藏域
- URL 改写
- HttpSession
本文将主要演示 Cookie
和 HttpSession
的用法。
Cookie
什么是 cookie
Cookie
是一串可以持久化于各个请求之间的信息片段。每个 cookie 都有一个名字,并有一个值,同时可以包含备注、路径、域名、过期时间、版本等附加信息。
Cookie 有两种:
- 非持久 cookie,这种 cookie 只在会话中存留,并且不具有过期时间属性,一旦用户关闭浏览器 (或者标签页),也就是使这个会话失效,这个 cookie 就会丢失。
- 持久化 cookie,这种 cookie 可以被用于多个会话中,而且只会在到达过期时间,或者用户主动使该 cookie 失效后,才会被删除。
可以使用 HttpServletResponse#addCookie(Cookie)
方法在 HTTP 响应中携带 cookie。
保存 cookie
首先修改前文中的 doPost()
方法,将请求中的参数取出来,并存入 cookie。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
Map parameterMap = request.getParameterMap();
try (PrintWriter writer = response.getWriter()) { writer.print("<html lang=\"en\">"); writer.print("<body>"); writer.print("<b>Response from DemoServlet</b>"); writer.print("<br>"); writer.print("<b>Handled by <code>doPost()</code></b>"); writer.print("<br>");
writer.print("<b>Session ID: " + request.getSession().getId() + "</b>"); writer.print("<br>");
parameterMap.forEach((k, v) -> { writer.print(k + " = " + ((String[]) v)[0] + "<br>");
response.addCookie(new Cookie(String.valueOf(k), ((String[]) v)[0])); });
writer.print("</body>"); writer.print("</html>"); } }
|
然后发送一个 POST
请求,在返回中可以看到请求中的参数已经被放到 cookie 中,并返回到了客户端。
使用 cookie
一旦 cookie 被保存到了客户端,那么在下次访问这个 cookie 所对应的地址时,客户端就会自动将相关的 cookie 带入请求一并发送到服务端。所以客户端不需要对 cookie 主动做任何操作。
修改前文中的 doGet()
方法,使其可以取出 cookie 的值,并输出到页面上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
Optional<String> optionalQueryString = Optional.ofNullable(request.getQueryString());
String[] queryStrings = optionalQueryString.isPresent() ? optionalQueryString.get().split("&") : new String[]{};
try (PrintWriter writer = response.getWriter()) { writer.print("<html lang=\"en\">"); writer.print("<body>"); writer.print("<b>Response from DemoServlet</b>"); writer.print("<br>"); writer.print("<b>Handled by <code>doGet()</code></b>"); writer.print("<br>");
writer.print("<b>Session ID: " + request.getSession().getId() + "</b>"); writer.print("<br>");
writer.print("<br>");
writer.print("<b>Parameters: </b>"); writer.print("<br>");
for (String query : queryStrings) { String[] q = query.split("=");
writer.print(q[0] + " = " + q[1] + "<br>"); }
writer.print("<br>"); writer.print("<b>Cookies:</b>"); writer.print("<br>");
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) { writer.print(cookie.getName() + " = " + cookie.getValue()); writer.print("<br>"); }
writer.print("</body>"); writer.print("</html>"); } }
|
然后发送一个 GET
请求,在返回中可以看到 cookie 中的内容已经被输出到页面上。
删除 cookie
将 cookie 的存活时间设为 0,并返回到客户端,即可从客户端中删除这个 cookie。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) { if (!"JSESSIONID".equalsIgnoreCase(cookie.getName())) { cookie.setMaxAge(0);
resp.addCookie(cookie); } }
try (PrintWriter writer = resp.getWriter()) { writer.print(""); } }
|
HttpSession
什么是 session
Session 记录着一次会话相关的信息。
当一个请求到达服务器后,服务器会检查请求中是否包含 session ID 信息,比如在 Tomcat 中就是检查有无 JSESSIONID
这个 cookie,或者 URL 中有无 JSESSIONID
这个查询字符串。如果找到了对应的 session,则服务器会将这个 session 检索出来使用;请求中没有包含 session ID,或者对应的 session 已经被销毁,则服务器会创建一个新的 session 并返回其 ID。
Session ID 通常以 cookie 的形式返回到客户端,如果客户端禁用了 cookie,那么服务端则会使用 URL 重写技术将 session ID 写到 URL 中。
Session 中可以键值对的形式保存附加数据,称为 attributes。
与 cookie 不同,session 保存于服务器端,而且它能保存的数据也不仅限于字符串。
保存 attribute
修改 doPost()
方法,编写修改 session 的代码。修改完成后发送一个带有参数的 POST
请求,以向 session 中写入一些数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
Map parameterMap = request.getParameterMap();
HttpSession session = request.getSession();
try (PrintWriter writer = response.getWriter()) { writer.print("<html lang=\"en\">"); writer.print("<body>"); writer.print("<b>Response from DemoServlet</b>"); writer.print("<br>"); writer.print("<b>Handled by <code>doPost()</code></b>"); writer.print("<br>");
writer.print("<b>Session ID: " + request.getSession().getId() + "</b>"); writer.print("<br>");
parameterMap.forEach((k, v) -> { writer.print(k + " = " + ((String[]) v)[0] + "<br>");
Cookie cookie = new Cookie(String.valueOf(k), ((String[]) v)[0]); response.addCookie(cookie);
session.setAttribute(String.valueOf(k), ((String[]) v)[0]); });
writer.print("</body>"); writer.print("</html>"); } }
|
取出 attribute
修改 doGet()
方法,使其可以从 session 中取出 attributes 并显示在页面上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
Optional<String> optionalQueryString = Optional.ofNullable(request.getQueryString());
HttpSession session = request.getSession();
Enumeration<String> attributeNames = session.getAttributeNames();
String[] queryStrings = optionalQueryString.isPresent() ? optionalQueryString.get().split("&") : new String[]{};
try (PrintWriter writer = response.getWriter()) { writer.print("<html lang=\"en\">"); writer.print("<body>"); writer.print("<b>Response from DemoServlet</b>"); writer.print("<br>"); writer.print("<b>Handled by <code>doGet()</code></b>"); writer.print("<br>");
writer.print("<b>Session ID: " + request.getSession().getId() + "</b>"); writer.print("<br>");
writer.print("<br>");
writer.print("<b>Parameters: </b>"); writer.print("<br>");
for (String query : queryStrings) { String[] q = query.split("=");
writer.print(q[0] + " = " + q[1] + "<br>"); }
writer.print("<br>"); writer.print("<b>Cookies:</b>"); writer.print("<br>");
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) { writer.print(cookie.getName() + " = " + cookie.getValue()); writer.print("<br>"); }
writer.print("<br>"); writer.print("<b>Attributes: </b>"); writer.print("<br>");
while(attributeNames.hasMoreElements()) { String key = attributeNames.nextElement();
String value = String.valueOf(session.getAttribute(key));
writer.print(key + " = " + value); writer.print("<br>"); }
writer.print("</body>"); writer.print("</html>"); } }
|
然后发送一个 GET
请求,在返回中就可以看到刚才保存在 session 中的数据:
删除 attribute
此外 HttpSession
类提供了 removeAttribute()
方法用于删除一个 attribute。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Cookie[] cookies = req.getCookies();
HttpSession session = req.getSession();
Enumeration<String> attributeNames = session.getAttributeNames();
for (Cookie cookie : cookies) { if (!"JSESSIONID".equalsIgnoreCase(cookie.getName())) { cookie.setMaxAge(0);
resp.addCookie(cookie); }
while(attributeNames.hasMoreElements()) { String key = attributeNames.nextElement();
session.removeAttribute(key); } }
try (PrintWriter writer = resp.getWriter()) { writer.print(""); } }
|
系列博文