前言
我们在前面几章中学到,包含 JavaScript(或者嵌入了其他技术,如 Java 小应用程序)的 HTML 文档可以改变文档的浏览器表示的内容和结构,来响应多种不同的事件、弹出式对话框窗口。HTML 与 JavaScript 和 DOM 的结合有时被称为 动态 HTML (DynamicHTML,DHTML),包含脚本的 HTML 文档被称为 动态(dynamic)文档。另一方面,不包含脚本的简单 HTML 文档则被称为 静态(static)文档。
在本章中,我们将继续学习有关基于 Web 的系统的编程知识。从涉及 Web 浏览器的客户端编程转向服务器端编程。而其中这章主要涉及的是 Java servlet 编程,它可以定制服务器来给 Web 用户提供某些服务。
servlet 简介
正如前言中写到的,servlet 是一个可用于在接收到 HTTP 请求时动态生成响应的许多技术之一(动态生成 HTML 的技术)。实质上讲,servlet 只是一个 Java 类,在启动服务器时由 Web 服务器对其进行实例化。当服务器接收到某些 HTTP 请求时,将在这个实例上调用特定的方法。
它的工作原理很简单,分为 4 步:
- 客户端发送 HTTP 请求到服务端;
- 服务器将请求或响应的对象发送给 servlet 程序(它是一个 Java 程序);
- servlet 将修改后的响应对象发送回服务器;
- 服务器从动态信息中创建一个 HTTP 响应发送回客户端。

下面是一个非常简单的 servlet 例子,用于输出 “Hello World”,这里仅仅讲解代码部分,在 Tomcat 服务器实践部分请见:
https://hoyue.fun/web_servlet.html
import java.io.*;import javax.servlet.*;import javax.servlet.http.*;
/** * Hello World! servlet */public class ServletHello extends HttpServlet{ /** * Respond to any HTTP GET request with an * HTML Hello World! page. * @param request * @param response * @throws javax.servlet.ServletException * @throws java.io.IOException */ @Override public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Set the HTTP content type in response header response.setContentType("text/html; charset=\"UTF-8\"");
// Create the body of the response try ( // Obtain a PrintWriter object for creating the body // of the response PrintWriter servletOut = response.getWriter()) { // Create the body of the response servletOut.println( "<!DOCTYPE html \n" + " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> \n" + "<html xmlns='http://www.w3.org/1999/xhtml'> \n" + " <head> \n" + " <title> \n" + " ServletHello.java \n" + " </title> \n" + " </head> \n" + " <body> \n" + " <p> \n" + " Hello World! \n" + " </p> \n" + " </body> \n" + "</html> "); servletOut.close(); } }}servlet 程序在使用 doGet() 方法的时候,必须遵循以下步骤:
public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
为了响应 GET 请求,servlet 类必须重写 HttpServlet 的doGet()方法。servlet 方法执行,通常调用服务器传递给它的 HttpServletRequest(接受请求) 和 HttpServletResponse(接受响应) 对象上的方法,并且可能会抛出 ServletException 和 IOException 异常。例如从请求中获取参数或者向响应中写入数据。response.setContentType("text/html; charset=\"UTF-8\"");
在这个例子中,响应的内容类型被设置为 “text/html”,并且字符集被设置为 “UTF-8”。这意味着响应的内容将是 HTML 格式的,并且使用 UTF-8 字符集进行编码。这个方法应该在处理 HTTP 响应 之前 被调用,以确保响应的内容类型被正确设置。try (PrintWriter servletOut = response.getWriter())
使用 try-with-resources 语句创建了一个 PrintWriter 对象,它被初始化为response.getWriter()的返回值。PrintWriter 对象可以用于向 HTTP 响应中写入文本数据。这个操作必须在setContentType()设置好 Content-type 之后才能调用。servletOut.println()把一个有效的 HTML 文档输出到 PrintWriter 对象servletOut中。servletOut.close();最后关闭 PrintWriter 对象。
servlet 没有一个 main 方法,因为 Tomcat 服务器提供了 main。作为替代,doGet() 或 doPost() 充当 servlet 的一种 main 方法。servlet 的主要输出通常是 HTML,对于其他 Java 程序则不一定。
参数数据
导航到与一个 servlet 关联的 URL 可以被视作类似于在 Java 中调用一个方法。也就是说,当我们导航到该 URL 时,服务器就会为我们调用 servlet 的 doGet() 方法,并且实质上会返回一个(较长的 HTML) 字符串,表示该方法的“返回值”。对 Java 方法的实际调用可以包含 URL 参数值。例如:
http://www.example.com/servlet/PrintThis?arg=aString
在 URL 内,问号(?) 标志着 URL 的路径部分的结束和 URL 的查询部分的开始,并且 URL 的查询部分包含一个查询字符串 (query string)。在这种情况下查询字符串包含一个名为 arg 的参数(parameter) 它被赋予字符串值 aString。
对于多个参数,可以通过用“与”符号(&) 分隔相邻的 参数名=值 对。例如,如下 URL 的查询字符串包含两个参数:
http://www,example.com/servlet/PrintThis?arg=aString&color=red
其中参数的顺序无关紧要。
如果我们想给参数赋值一个空字符,我们可以直接在参数名后跟一个 ”=” 即可。例如:
http://www.example.com/servlet/PrintThis?arg=&color=redhttp://www.example.com/servlet/PrintThis?color=red&arg=
如果参数值是一个非字母数字字符,那么需要将其转义后写为值。例如我们 arg 参数的参数值为 ” ‘a String’ “:http://www.examplecom/servlet/PrintThis?arg=%27a+String%27 转义的过程称为 URL 编码,转换回正常的字符串的形式称为 URL 解码。
在 servlet 中,有一些可以访问参数数据的 HttpServletRequest 方法如下:

会话(Sessions)
为了便于使用,许多 Web 站点被设计成通过一系列页面(而不是一个大页面) 从站点访问者那里获得信息。例如,用户可能在一个页面上输入产品订购信息,在另一个页面上输入送货地址,并在第三个页面上输入信用卡信息。但是,如果 web 站点正在处理上百个用户的请求,服务器怎样知道哪些 HTTP 请求来自于哪些用户呢?
我们知道 HTTP(超文本传输协议)是一种无状态协议,这意味着它不会在客户端和服务器之间维护任何有关先前请求或会话的信息。每个请求都被视为一个新请求,没有任何先前请求的知识。
在 Web 应用程序中,Session 是一种 服务器端 技术(由服务器存储),用于跟踪用户在不同页面和请求之间的状态。Session 允许服务器在用户访问网站时创建一个唯一的标识符(session ID),并将该标识符与用户的会话信息相关联。这个会话信息可以包括任何需要在不同页面之间共享的数据,例如用户的登录状态、购物车中的商品、用户的偏好设置等。
对于新用户如果请求不包含一个 session ID,那么就假定请求来自一个新用户,并且 Web 服务器会生成一个新的唯一 session ID 与这个用户相关联。当 Web 服务器创建 HTTP 响应消息时,将作为响应的一部分包含会话 ID 一起返回。
客户端(浏览器)接收到 session ID 后,会存储到客户端中,在下次再次访问服务端的时候会作为 HTTP 请求的一部分将 session ID 发送给服务端。服务端将识别来自单个用户的所有 HTTP 请求,并和其他用户独立。如下图是服务端与两个客户端的 session ID 发送以及响应的过程:

每个客户第一次发送请求的时候,服务端都会返回一个 session ID,在下次客户端请求时,客户端会同时发送 session ID 以便服务端识别不同客户响应不同的内容。
创建会话
服务器通过把一个 HttpSession 对象与服务器维护的每个会话相关联,来支持 session。每个对象都会存储其会话的会话 ID,还会存储其他与会话相关的信息。
当 servlet 在其 HttpServletRequest 参数上调用 getSession() 方法,它会有两种情况:
- 关联的 HTTP 请求不包含一个有效的 session ID 时服务器就会创建一个 HttpSession 对象,并分配一个 session ID;
getSession()方法会返回 最近创建 的对象的 session ID。 - 如果 HTTP 请求包含一个有效的会话 ID,那么调用
getSession()将会 返回以前创建的 HttpSession 对象,其中包含这个会话 ID。
servlet 可以通过在 HttpSession 对象上调用布尔方法 isNew(),来确定 HttpSession 返回的 HttpSession 对象是否是最新创建的。
WARNINGHttpSession 对象是一个对象,isNew() 是它的方法;getSession() 是 HttpServletRequest 对象的方法,而非 HttpSession,getSession() 的返回值是一个 HttpSession 对象。
我们可以通过 HttpSession 对象的 getId() 方法获取 session ID。
下面是一个统计访问者数量的 servlet 程序代码,统计访问者和统计访问数不一样,统计访问者是统计用户数,同一个用户的 session ID 相同,只有在不同的 session ID 访问时计数器才会加一。
public class VisitorCounter extends HttpServlet{ // Number of visitors to the servlet since // the program (web server) started private int visits=0;
/** * Respond to any HTTP GET request with an * HTML Hello World! page with visitor counter. * @param request * @param response * @throws javax.servlet.ServletException * @throws java.io.IOException */ @Override public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Set the HTTP content type in response header response.setContentType("text/html; charset=\"UTF-8\"");
// Determine whether or not this is the first visit // by this user, and update visit count if this is // the first visit try ( // Obtain a Pr/*i*/ntWriter object for creating the body // of the response PrintWriter servletOut = response.getWriter()) { // Determine whether or not this is the first visit // by this user, and update visit count if this is // the first visit HttpSession session = request.getSession(); if (session.isNew()) { visits++; }
// Create the body of the response, including visit count servletOut.println( "<!DOCTYPE html \n" + " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \n" + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> \n" + "<html xmlns='http://www.w3.org/1999/xhtml'> \n" + " <head> \n" + " <title> \n" + " VisitorCounter.java \n" + " </title> \n" + " </head> \n" + " <body> \n" + " <p> \n" + " Hello World! \n" + " </p> \n" + " <p> \n" + " This page has been viewed by \n" + visits + " visitors since the most recent server restart.\n And your session ID is " + session.getId() + " </p> \n" + " </body> \n" + "</html> "); servletOut.close(); } }}HttpSession session = request.getSession();:使用HttpServletRequest的getSession()方法作为初始值,并创建了一个 HttpSession 对象 session 用来存储 session。session.isNew()用于检测是否这个 session 是新创建的,如果是新创建的话,则返回 TRUE,这个时候统计一个用户。session.getId()用于取得 session ID。
当我们正确运行该 servlet 程序时,就可以看到如下页面:

此时无论我们怎么刷新,计数器依然是 1,因为在同一个客户端中,你的 session ID 相同,只有新的 session ID 才会增加。
我们可以打开无痕浏览(隐私模式下,之前的 session ID 会清除,会视为一个新的客户端)或者是更换一个浏览器再次访问,这时就可以看到计数器加一了,且 session ID 更变。

Cookie
Cookie 是 HTTP 响应的 Set-Cookie 标头字段中的名称/值对,它是由 Web 服务器通过用户使用的 Web 浏览器 存储在用户计算机 上的小文本文件。当用户访问同一网站时,该网站可以从用户的计算机读取该 Cookie,以便在用户不断地浏览网站时跟踪用户的活动并执行其他功能。
Cookie 和 Session 都是 Web 应用程序中用于跟踪用户状态的技术,但它们有一些区别和各自的用途。
- Cookie 是在 客户端(即用户的浏览器)上存储数据的一种机制。当 Web 服务器向客户端发送响应时,可以包含一个或多个 Cookie,以便客户端在将来的请求中将这些 Cookie 发送回服务器。Cookie 通常用于存储用户的个人偏好设置、登录状态、以及其他与用户相关的数据。由于 Cookie 是存储在客户端上的,因此它们对于跨多个页面的状态跟踪非常有用。
- Session 则是在 服务器端 存储数据的一种机制。当用户在 Web 应用程序中进行交互时,Web 服务器会创建一个会话(session),并为该会话分配一个唯一的标识符(session ID)。服务器可以将该 session ID 存储在 Cookie 中,以便在将来的请求中识别客户端。通过 session ID,服务器可以在多个页面之间跟踪用户的状态,并存储与用户相关的数据。与 Cookie 不同,Session 数据存储在服务器上,因此它们对于存储敏感信息(例如用户的身份验证信息)非常有用。
Web 应用程序会同时使用 Cookie 和 Session 来跟踪用户状态。例如,当用户登录时,Web 服务器可能会创建一个 Session,将用户的身份验证信息存储在 Session 中,并将 Session ID 存储在 Cookie 中(cookie 还包含其他信息)。在将来的请求中,客户端将 Cookie 发送回服务器,服务器可以使用 Session ID 来查找该用户的 Session,并恢复该用户的身份验证信息。
在 servlet 中,我们可以通过 request.getCookies() 方法取得客户端请求里包含的 cookie。在响应中可以使用 response.addCookie(Cookie) 方法向响应中添加 cookie。
除此之外还有如下类方法:

当然具体使用起来和 session 差不多。