第3章 请求与响应 C/S架构的程序,其数据请求和数据处理都在客户端完成,只在数据存取时访问服务器后台,设计过程相对简单; B/S架构的程序,其数据请求在客户端的浏览器实现,该请求要发送到后台Web服务器,数据处理与响应由后台服务器完成,最终的处理结果再发送回客户端浏览器。因此,B/S架构的Web程序设计存在两大技术难点,一个是如何把客户端的请求数据发送到服务器,另一个是服务器将请求所需数据处理完成后如何把结果返回给客户端浏览器。 视频讲解 3.1请求响应模型 客户端的JSP页面一般是将页面数据和请求信息放置在<form>标签内部向Web服务器完成表单提交,利用<form>标签的action属性指定请求提交的目的处理程序,该程序一般是某个Servlet或另外的JSP页面。 Servlet最主要的功能就是处理客户端请求,并将处理结果返回给客户端,该过程称为响应。客户端浏览器访问Servlet的具体过程如图31所示,在该过程中Java Web程序处理请求响应的具体流程描述如下。 图31浏览器访问Servlet对应的请求响应模型 (1) 客户端的浏览器通过JSP页面中的form表单将数据进行封装,然后向Web服务器发送HTTP请求。 (2) Web服务器接收到客户端发来的HTTP请求,创建HttpServletRequest对象,将form表单中的数据和请求信息拆解并重新封装到HttpServletRequest对象中。 (3) Web服务器在创建HttpServletRequest对象的同时,一并创建HttpServletResponse对象,用来接收即将返回的响应信息。 (4) Web服务器根据form表单中action属性设定的Servlet映射调用对应Servlet的service(HttpServletRequest request, HttpServletResponse response)方法进行请求处理,该方法中的两个参数request和response对应步骤(2)中的HttpServletRequest对象和步骤(3)中的HttpServletResponse对象。 (5) service()方法将请求处理完毕后将所需的响应信息封装到response参数中,该参数负责将响应信息写入到步骤(3)中创建的HttpServletResponse对象。 (6) Web服务器读取HttpServletResponse对象中的响应数据,将响应数据封装到特定的页面中返回给客户端浏览器。 上述过程中产生的HttpServletRequest对象用于封装HTTP请求信息,简称request对象; HttpServletResponse对象用于封装HTTP响应信息,简称response对象。这两个对象作为JSP九大内置对象在后续章节还会深入讨论。 需要注意的是,在Web服务器运行期间,每个Servlet类只会创建一个实例对象,该对象负责处理多个HTTP请求并做出响应。但对于每次HTTP请求,Web服务器都会调用一次Servlet实例对象的service(HttpServletRequest request, HttpServletResponse response)方法,每调用一次service方法就会重新创建一个request对象和一个response对象。 视频讲解 3.2HttpServletRequest对象 HttpServletRequest是一个继承自ServletRequest接口的子接口,主要用于封装HTTP的请求信息。HTTP请求消息分为请求行、请求消息头和请求消息体三部分,如表31所示。 表31HTTP消息结构 消 息 部 分说明 请求行或状态行指定请求或响应消息的目的 请求头或响应头指定元信息,如关于消息内容的大小、类型、编码方式等 空行 消息体请求或响应消息的主要内容,可以为空 表32给出了一个典型的POST请求中的详细请求信息。 表32典型的POST请求信息 组 成 部 分说明 请求行POST/WebJDBC/CheckLoginServlet HTTP/1.1 请求头 Accept=*/* AcceptLanguage=zhch AcceptEncoding=gzip,deflate UserAgent=Mozilla/4.0(compatible; MSIE 9.0; Windows NT 5.1; SV1;.NET CLR 1.1.4322; .NET CLR 2.0.50727) Host=localhost:8080 Connection=KeepAlive 空行消息体可以为空 HttpServletRequest接口中主要定义了获取这三部分数据信息的相关函数。 3.2.1获取请求行 客户端浏览器访问Servlet时,会发送相应的请求消息给服务器,该请求消息分为请求行、请求消息头和请求消息体三部分。其中,在请求行部分包含请求方法名、请求资源的URL和HTTP版本等信息,这三部分由空格隔开。如表32中第一行数据所示,请求方法为POST,请求资源的URI为/WebJDBC/CheckLoginServlet,使用的协议及其版本为HTTP/1.1。 HttpServletRequest接口中定义了获取请求行信息的相关方法,如表33所示。 表33获取请求行信息的方法 方法说明 String getMethod()获取HTTP请求的请求方式(如GET、POST等) String getRequestURI() 获取请求行中资源名称部分,即位于URL的主机和端口之后、参数之前的部分 String getQueryString()获取请求行中的参数部分,即资源路径后面问号以后的所有内容 String getProtocol()获取请求行中的协议名及版本,如HTTP/1.0或HTTP/1.1 String getContextPath()获取URL中Web应用程序的路径 String getServletPath()获取Servlet的名称或Servlet所映射的路径 String getRemoteAddr(0)获取客户端的IP地址 String getRemoteHost()获取客户端的完整主机名 int getRemotePort()获取客户端的访问端口号 String getLocalAddr()获取服务器IP地址 String getLocalName()获取服务器主机名 int getLocalPort()获取服务器端口号 String getServerName() 获取当前请求所指向的主机名,即HTTP请求消息中Host头字段所对应的主机名部分 int getServerPort()获取当前请求所连接的服务器端口号 String getScheme()获取请求的协议名,如http、https或ftp等 StringBuffer getRequestURL()获取客户端发送请求的完整URL(不含参数部分) 【例31】测试HttpServletRequest对象获取请求行相关函数。 (1) 在Eclipse中新建一个名为“0301RequestLineInfo”的项目。 (2) 新建一个名为“GetRequestLineInfoServlet”的Servlet,放置在cn.pju.servlets包下,详细代码如下。 package cn.pju.servlets; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/GetRequestLineInfoServlet") public class GetRequestLineInfoServlet extends HttpServlet { private static final long serialVersionUID = 1L; public GetRequestLineInfoServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("getMethod(): " + request.getMethod()); System.out.println("getRequestURI(): " + request.getRequestURI()); System.out.println("getRequestURL(): " + request.getRequestURL()); System.out.println("getQueryString(): " + request.getQueryString()); System.out.println("getProtocol(): " + request.getProtocol()); System.out.println("getContextPath(): " + request.getContextPath()); System.out.println("getServletPath(): " + request.getServletPath()); System.out.println("getRemoteAddr(): " + request.getRemoteAddr()); System.out.println("getRemoteHost():" + request.getRemoteHost()); System.out.println("getRemotePort():" + request.getRemotePort()); System.out.println("getLocalAddr():" + request.getLocalAddr()); System.out.println("getLocalName():" + request.getLocalName()); System.out.println("getLocalPort():" + request.getLocalPort()); System.out.println("getServerName():" + request.getServerName()); System.out.println("getServerPort():" + request.getServerPort()); System.out.println("getScheme():" + request.getScheme()); System.out.println("getRequestURL():" + request.getRequestURL()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } (3) 将该项目配置到Tomcat服务器,然后运行GetRequestLineInfoServlet,在浏览器中输入http://localhost:8080/0301RequestLineInfo/GetRequestLineInfoServlet?info=justatest,查看Console控制台的输出信息,如图32所示。 图32HttpServletRequest函数运行结果 由于在访问过程中使用的是本机主机名localhost,所以getRemoteAddr()、getRemoteHost()、getLocalAddr()和getLocalName()函数返回的结果都是“0:0:0:0:0:0:0:1”,即IPv6中的本机地址[::1]。用户可以将localhost修改为本机的外部访问IP地址或局域网IP地址(在命令提示符下输入“ipconfig/all”进行查看),如作者当前计算机的局域网IP地址为192.168.0.111,所以在浏览器中输入如下URL地址http://192.168.0.111:8080/0301RequestLineInfo/GetRequestLineInfoServlet?info=justatest,则对应的测试结果如图33所示。 图33使用局域网IP地址测试HttpServletRequest函数运行结果 3.2.2获取请求消息头 HTTP请求消息中的请求消息头主要用来向服务器传递附加信息,例如,字符集编码、语言、压缩类型等。HttpServletRequest接口定义了常用的获取HTTP请求头字段信息的方法,见表34。 表34获取请求头信息的方法 方法说明 String getHeader(String name)获取HTTP请求中指定头字段的值,若不存在返回NULL; 存在多个时返回首个 Enumeration getHeaders(String name) 返回名为name的所有头字段值所组成的Enumeration集合对象 Enumeration getHeaderNames() 获取所有请求头字段组成的Enumeration对象 int getIntHeader(String name) 获取HTTP请求中名为name的头字段的值,并转换为int类型 续表 方法说明 long getDateHeader(String name) 获取HTTP请求中名为name的头字段的值,并转换为GMT时间格式的长整型 String getContentType() 获取头字段ContentType(内容类型,即文件的类型和网页的编码)的值 int getContentLength() 该方法用于获取ContentLength头字段的值 String getCharacterEncoding() 返回请求消息实体部分的字符集编码 【例32】测试HttpServletRequest对象获取请求头相关函数。 (1) 在项目0301RequestLineInfo中新建一个名为GetRequestHeaderInfoServlet的Servlet,放置在cn.pju.servlets包下,详细代码如下。 package cn.pju.servlets; import java.io.IOException; import java.util.Enumeration; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/GetRequestHeaderInfoServlet") public class GetRequestHeaderInfoServlet extends HttpServlet { private static final long serialVersionUID = 1L; public GetRequestHeaderInfoServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); //循环遍历所有的请求头,并输出请求信息 Enumeration hs = request.getHeaderNames(); while(hs.hasMoreElements()) { String hName = (String) hs.nextElement(); System.out.println(hName + " : " + request.getHeader(hName)); } System.out.println(request.getCharacterEncoding()); } protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { doGet(request, response); } } (2) 在浏览器中输入网址http://localhost:8080/0301RequestLineInfo/GetRequestLineInfoServlet?info=justatest,运行GetRequestHeaderInfoServlet,查看Console控制台的输出信息,如图34所示。 图34HttpServletRequest请求头信息结果 3.2.3相关应用 1. 获取请求参数 在Web项目开发中,通常需要通过前端页面中的form表单向后台服务器传递数据,例如登录界面中的用户名、密码、随机验证码等。这些参数传递到后台之后,可以被HttpServletRequest接口及其父类ServletRequest对应的一系列方法进行获取。获取请求参数的方法如表35所示。 表35获取请求参数的方法 方法说明 String getParameter(String name)获取HTTP请求中指定名称为name的参数值,若不存在返回NULL; 如存在但未设置值,则返回空串; 如果存在多个时返回首个 String[] getParameterValues(String name)若传递的HTTP请求中存在多个名为name的参数值,使用该函数读取所有的参数值,放入一个字符串数组后返回 Enumeration getParameterNames()返回HTTP请求中由所有参数名所组成的Enumeration集合对象,利用本函数及以上两个函数可以实现对请求消息中所有参数的遍历操作 Map getParameterMap()将请求消息中所有的参数名和对应的参数值封装进一个Map对象并返回 【例33】利用HttpServletRequest对象获取客户端传入的指定名称参数值。 (1) 新建名为0303RequestApp的动态Web项目。 (2) 在WebContent目录下新建regist.jsp页面,代码如下。 <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%> <%@taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%> <%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>用户注册</title> </head> <body> <form action="/0303-RequestApp/RequestParamsServlet" method="post"> 账号:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> 研究方向: <input type="checkbox" name="researcharea" value="hardware">计算机硬件 <input type="checkbox" name="researcharea" value="software">软件工程 <input type="checkbox" name="researcharea" value="bigdata">大数据<br> <input type="submit" value="提交"> </form> </body> </html> (3) 新建cn.pju.requestapp.svlt包,在该包下新建名为RequestParamsServlet的Servlet类,代码如下。 package cn.pju.requestapp.svlt; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/RequestParamsServlet") public class RequestParamsServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); String[] researcharea = request.getParameterValues("researcharea"); System.out.println("用户名: " + username); System.out.println("密码: " + password); System.out.println("研究领域: "); for (String ra : researcharea) { System.out.println(ra); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } (4) 将项目配置到Tomcat容器上,启动服务器,在浏览器中输入访问地址http://localhost:8080/0303RequestApp/regist.jsp,打开如图35所示的用户注册界面。 图35用户注册界面 (5) 在账号栏和密码栏中分别输入admin和sysman,全选三个研究方向复选框,单击“提交”按钮,观察后台Console窗口数据显示,如图36所示。 图36运行结果 2. 解决请求参数的中文乱码问题 如果在如图35所示的用户注册界面输入中文字符的账号(比如“张俊”),再次单击“提交”按钮发送至后台的Servlet程序处理,则解析结果为乱码,如图37所示。 图37中文参数值解析出现乱码 在此分析一下,客户端浏览器向后台服务器传递中文参数值,出现中文乱码现象的原因。 首先,当客户端浏览器表单中输入了中英文参数值并通过表单提交按钮向后台服务器提交时,浏览器会根据默认设置的编码格式对参数值进行封装,不同浏览器的默认字符编码不尽相同,而且用户可以根据自身需要进行调整设置。以IE浏览器为例,在客户端输入中文账号,在浏览器空白区域右击,展开“编码”菜单,可见浏览器当前使用的默认编码方式为Unicode(UTF8),如图38所示,也可根据需要切换简体中文(GB2312)字符编码格式。浏览器设置的字符编码不同,输入框中的数据传递给form表单的实际二进制编码内容也不同。 图38浏览器编码格式 其次,客户端封装后的数据传递到后台服务器,由HttpServletRequest对象进行数据解析,解码时也会用到字符编码,如果解码时所使用的字符编码与封装时使用的字符编码不一致,就会出现中文乱码现象。在Servlet中可以使用函数setCharacterEncoding(String encoding) 来设置Request对象编码格式,如request.setCharacterEncoding("UTF8"),需要注意的是该语句必须写在doGet()函数的首行,这是因为doGet()函数在执行函数代码之前首先检测是否有字符编码设置函数语句进行了编码设置,如果未发现则默认采用ISO88591编码格式作为首选,然后继续执行函数体内其他语句,当再次遇到setCharacterEncoding()函数设置不同代码时,将忽略该语句。 最后,客户端参数的编码封装格式还与form表单的提交方式有关,这是因为setCharacterEncoding()函数只对post方式提交的表单有效,对get方式提交的表单无效。 有的读者可能会产生疑问,为什么英文字母和阿拉伯数字不存在乱码问题,而唯独中文会出现乱码?这是因为英文字母和阿拉伯数字数量相对较少,所以在ISO88591、Unicode(UTF8)和简体中文(GB2312)等常用编码中,这些符号的编码都与其ASCII码值保持一致,也就是说在这些编码机制中,其编码都是相同的,所以无论使用何种编码进行封装和解析,都不会出现乱码现象。而中文具有其特殊性,在不同的编码格式中,同一个汉字对应的编码是不同的。比如在A编码格式中,汉字“中”存储在x行y列,我们将其编码设定为xy,而在B编码格式中,xy编码对应的x行y列存储的可能是另一个汉字或者是某个汉字的一部分,此时就会出现乱码或前后解析不符的现象。其实不光中文数据在客户端与服务器端编码不一致时会产生乱码现象,常见的CJK编码都会出现类似现象,这是由这些语言的特殊编码格式所决定的。 总结以上分析可见,客户端浏览器将表单数据进行封装然后向后台服务器传递中文参数值,服务器端由Request对象再进行数据解析,出现乱码现象与客户端浏览器编码格式、表单提交方式和服务器端Request对象进行解析时使用的CharacterEncoding编码格式三个因素有关。在实际使用过程中,只要三者设置保持同一种编码或相互兼容的编码格式,就可避免中文解析出现乱码现象。修改后的源程序文件RequestParamsServlet.java代码如下。 package cn.pju.requestapp.svlt; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/RequestParamsServlet") public class RequestParamsServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String[] researcharea = request.getParameterValues("researcharea"); System.out.println("用户名: " + username); System.out.println("密码: " + password); System.out.println("研究领域: "); for (String ra : researcharea) { System.out.println(ra); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 3. 通过Request对象传递数据 在客户端的浏览器通过表单中的控件向后台Servlet传递的数据一般被当作Parameter参数保存,如果想向Request对象中插入其他非参数变量,则可使用setAttibute()方法设置参数,使用getAttribute()函数进行参数值的读取。Request对象常用的属性操作函数如表36所示。 表36Request对象常用的属性操作函数 方法说明 void setAttribute(String name, Object obj)向ServletRequest对象中写入名为name的属性并设置其值为obj; 若已存在name属性,则覆盖; 若obj值为null,则删除指定属性,效果等同于removeAttribute() Object getAttribute(String name)获取ServletRequest对象中名为name的属性值 removeAttribute(String name)在ServletRequest对象中删除名为name的属性 Enumeration getAttributeNames()返回一个包含ServletRequest对象中所有属性的Enumeration对象 修改源程序文件RequestParamsServlet.java,将注册结果写入ServletRequest对象的registOK属性,代码如下。 package cn.pju.requestapp.svlt; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.sun.org.apache.xpath.internal.operations.And; @WebServlet("/RequestParamsServlet") public class RequestParamsServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String[] researcharea = request.getParameterValues("researcharea"); System.out.println("用户名: " + username); System.out.println("密码: " + password); System.out.println("研究领域: "); for (String ra : researcharea) { System.out.println(ra); } //注册结果判断 if(username != null && !username.isEmpty() && password != null && !password.isEmpty() && researcharea != null && researcharea.length > 0 ) { request.setAttribute("registOk", true); } else { request.setAttribute("registOk", false); } if((Boolean)request.getAttribute("registOk")) { response.getWriter().println("注册成功"); } else { response.getWriter().println("注册失败"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 输入合法的用户名、密码,选择对应的研究领域选项,单击“提交”按钮,客户端浏览器反馈结果如图39所示。 图39注册成功提示 视频讲解 3.3HttpServletResponse对象 HTTP应答与HTTP请求相似,也由3个部分构成,分别是状态行、响应头(Response Header)和响应正文。在Servlet API中,一般通过HttpServletResponse接口将服务器端的信息传递给客户端,HttpServletResponse接口专门用来封装HTTP响应消息,它是ServletResponse接口的子接口。与HTTP响应消息分为状态行、响应消息头、消息体三部分相对应,HttpServletResponse接口中定义了向客户端发送响应状态码、响应消息头、响应消息体的三类方法。 3.3.1常用方法 1. 发送状态码相关的方法 Servlet向客户端浏览器回送Response响应消息时,需要在消息中设置一个状态码。HttpServlet Response接口定义了两个常用发送状态码的方法。 1) setStatus(int status)方法 设置HTTP响应消息状态码的值。Response响应状态行中的状态描述信息直接与状态码相关,如404状态码表示找不到客户端所请求的资源。正常情况下,Web服务器会默认产生一个状态码为200的状态行。状态行由协议版本、数字形式的状态代码,及相应的状态描述组成,各元素之间以空格分隔。而HTTP版本由服务器确定,因此只要通过setStatus(int status)方法设置了状态码,即可实现状态行的发送。 2) sendError(int code)方法 当需要向客户端发送错误状态码时可使用此方法,比如sendError(404) 表示找不到客户端请求的资源。该方法提供了以下两个重载格式。 public void sendError(int code) throws java.io.IOException public void sendError(int code, String message) throws java.io.IOException 其中,sendError(int code)方法只是发送错误信息的状态码,sendError(int code, String message)方法在发送错误状态码code的同时还增加了一条用于提示说明的文本信息message,该文本信息将会显示在发送给客户端的正文内容中。 2. 发送响应消息头相关的方法 由于HTTP有多种响应头字段,所以在 HttpServletResponse接口中对应定义了一系列设置HTTP响应头字段的方法,如表37所示。 表37设置响应消息头字段的相关方法 方法说明 void addHeader(String name, String value)添加响应头的名字,以及与响应头名字对应的值。如果name对应的响应头不存在,则新增一个; 如果响应头已存在,则增加一个同名的name响应头,HTTP响应消息中允许同一名称的头字段出现多次 void setHeader(String name, String value)设置与响应头名字对应的值。如果name对应的响应头不存在,则新增一个; 如果响应头已存在,则将用新的设置值取代原来的设置值 void addIntHeader(String name, int value)添加响应头的名字,以及与响应头名字对应的整型值。函数用法与addHeader(String name, String value)类似,仅用于设置响应头对应值为整型时的情况 void setIntHeader(String name, int value)设置与响应头名字对应的整型值。函数用法与setHeader(String name,String value)类似,仅用于设置响应头对应值为整型时的情况 void setContentLength(int len)设置响应消息中实体内容的长度,单位是字节。对于HTTP来说,该方法等价于设置ContentLength响应头字段的值 void setContentType(String type)设置Servlet输出内容的MIME类型,对于HTTP而言,就是设置ContentType响应头字段的值。例如,如果发送到客户端的内容是jpeg图像数据时,就需要将响应头字段的类型设置为“image/jpeg”; 如果响应的内容为文本,需要使用setContentType()方法设置字符编码,如text/html;charset=UTF8 void setLocale(Locale loc)设置响应消息的本地化信息。即设置ContentLanguage响应头字段和ContentType头字段中的字符集编码。如果HTTP消息没有设置ContentType头字段,setLocale()方法设置的字符集编码就不会出现在任何HTTP消息的响应头中,如果调用setCharacterEncoding)或setContentType()方法指定了响应内容的字符集编码,setLocale()方法将不再具有指定字符集编码的功能 void setCharacterEncoding(String charset)设置输出内容使用的字符编码,即设置ContentType头字段中的字符集编码部分。如果没有设置ContentType头字段,setCharacterEncoding方法设置的字符集编码不会出现在HTTP消息的响应头中。SetCharacterEncoding()方法比setContentType()和setLocale()方法的优先权高,它的设置结果将覆盖setContentType()和 setLocale()方法所设置的字符码表 3. 发送响应消息体的相关方法 设置响应消息体的相关方法如表38所示。 表38设置响应消息体的相关方法 方法说明 ServletOutputStream getOutputStream()获取ServletOutputStream类型的字节输出流。该函数可以直接输出字节数组中的二进制数据,常用于输出二进制格式的响应正文 PrintWriter getWriter()获取PrintWriter类型的字符输出流对象,该对象可以直接输出字符文本内容 【例34】测试HttpServletResponse对象发送消息体相关函数。 (1) 在Eclipse中新建一个名为0304MyResponse的项目,并将整个项目的编码格式设置为UTF8。 (2) 新建一个名为MyPrintServlet的Servlet,放置在cn.pju.response包下,详细代码如下。 package cn.pju.response; import java.io.IOException; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/MyPrintServlet") public class MyPrintServlet extends HttpServlet { private static final long serialVersionUID = 1L; public MyPrintServlet() { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); //第一句,设置服务器端编码 response.setHeader("Content-Type","text/html;charset=UTF-8");//第二句,设置浏览器端解码 String data = "Java Web程序设计"; OutputStream out = response.getOutputStream(); out.write(data.getBytes()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } (3) 将项目配置到Tomcat容器上,启动服务器,在浏览器中输入访问地址http://localhost:8080/0304MyResponse/MyPrintServlet,运行结果如图310所示。 图310运行结果 (4) 修改程序代码,改用response对象的getWriter()方法获取PrintWriter对象,然后调用该对象的write(String data)方法直接输出字符型数据,代码如下。 package cn.pju.response; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/MyPrintServlet") public class MyPrintServlet extends HttpServlet { private static final long serialVersionUID = 1L; public MyPrintServlet() { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); String data = "Java Web程序设计"; PrintWriter writer = response.getWriter(); writer.write(data); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 程序运行效果同图310。 需要注意的是,response对象的getOutputStream()、getWriter()函数不可同时使用,否则会发生IllegalStateException异常。 3.3.2相关应用 1. 解决中文输出乱码问题 在Web项目开发过程中,经常会用到几种常见的字符编码,有ISO88591、UTF8、GBK、GBK2312、Big5等。英文字母和阿拉伯数字在这些编码表中的数值是一致的,所以采用不同的编码方式进行英文字母输出,不会出现乱码问题。而对于CJK字符集,在不同的字符编码集合中其对应的编码是不相同的,甚至有的字符编码集合不支持CJK字符,所以Java Web项目输出中文字符时,有可能出现乱码,其主要原因就是后台程序与前端浏览器所使用的字符编码不一致。 1) Java Web项目整个项目的编码格式 在Eclipse中选中当前项目,选择右击弹出菜单中的Properties,打开项目属性对话框,弹出如图311所示的属性设置窗口,选择左侧列表中的Resource,在右侧的Text file encoding属性中选择对应的编码格式。 图311项目编码设置 2) 后台源程序文件的编码格式 如果在项目开发过程中先设置了整个项目的编码格式,则后续新建的源代码文件都将继承项目的编码格式以保持项目本身的整体一致性。如新建项目之后立刻设置了项目的编码格式为UTF8,然后再创建源代码文件,则源代码文件的默认编码格式也会是UTF8。但也有读者是先创建了项目,然后创建了几个源程序文件之后才想起要设置项目的编码格式,但此时设置了项目编码格式之后,源程序文件的编码格式不会随着项目编码格式的改变而自动改变,这就需要手动设置源程序文件的编码格式,以保持与项目编码格式的一致。 在Project Explorer窗口选中需要设置的源代码文件,在右击弹出菜单中选择Properties,在如图312所示的源程序文件属性设置窗口中设置其编码格式。 图312源程序文件属性设置 3) 程序运行时后台服务器对应的编码格式 前端用户浏览器接收到的数据都是由后台服务器发送而来的,Web服务器需要将后台的数据封装打包发送,所以后台程序也有自己的编码格式,该编码格式使用response.setCharacterEncoding("UTF8")语句进行设置。 4) 前端页面输出时的编码格式 前端客户浏览器接收到数据之后,应按照服务器预期的编码格式进行解码,然后输出显示。在程序中通过response.setHeader("ContentType","text/html;charset=UTF8")设置浏览器的期望编码格式,有些浏览器设置了自动编码解析,只要在程序中设置了期望编码,浏览器就会自动切换相应的编码进行输出显示。 5) 用户端浏览器实际使用的编码格式 客户浏览器端的实际使用编码不受源程序的限制,用户可以通过如图313所示方法自行强制调整,但通常情况下,客户端浏览器是启用编码自动检测功能的。 图313浏览器端的编码设置 如果想避免中文字符编码乱码现象出现,建议以上五种编码方式尽量全部统一。在Servlet源程序代码中,一般使用以下两种方法进行编码设置,以确保避免乱码现象出现。 (1) 分别设置服务器端和客户端的编码方式。 response.setCharacterEncoding("UTF-8");//设置服务器端HttpServletResponse使用UTF-8编码 response.setHeader("Content-Type","text/html;charset=UTF-8");//通知浏览器使用UTF-8编码 (2) 使用setContentType()函数一次性设置,该函数包含以上两行代码的相同功能而且简洁明了,所以一般经常使用。 response.setContentType("text/html;charset=UTF-8"); 在例34的两种写法中,其实就分别使用了上述两种方法进行编码设置,读者可仔细阅读比较一下。 2. 解决中文输出乱码问题 在HTTP中定义了一个Refresh头字段,用来设置指定时间内自动刷新和到期后的页面跳转。 【例35】测试HttpServletResponse对象的定时刷新功能。 在项目0304MyResponse的源程序文件夹中新建一个名为RefreshServlet的Servlet类,代码如下。 package cn.pju.response; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/RefreshServlet") public class RefreshServlet extends HttpServlet { private static final long serialVersionUID = 1L; public RefreshServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Refresh", "5;URL=http://www.163.com"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 其中,response.setHeader("Refresh", "5;URL=http://www.163.com")语句表示设置页面5s之后跳转到http://www.163.com页面。分号前的5代表5s后进行刷新,如果在分号后添加了URL属性,则跳转到对应的页面。如果不设置URL属性,则只对当前页面进行刷新,不进行页面跳转。 例如,将以上程序修改为如下源代码,即可实现系统时间的每秒输出。 package cn.pju.response; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/RefreshServlet") public class RefreshServlet extends HttpServlet { private static final long serialVersionUID = 1L; public RefreshServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置每隔2s定期刷新 response.setHeader("Refresh", "2"); //输出服务器当前时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); response.getWriter().println(sdf.format(new java.util.Date())); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } 小结 本章主要介绍了HttpServletResponse对象和HttpServletRequest对象的使用,其中,HttpServletResponse对象封装了HTTP响应消息,并且提供了发送状态码、发送响应消息头、发送响应消息体的方法。使用这些方法可以解决中文输出乱码问题,实现网页的定时刷新跳转、请求重定向等。HttpServletRequest对象封装了HTTP请求消息,也提供了获取请求行、获取请求消息头、获取请求参数的方法。使用这些方法可以解决请求参数的中文乱码问题,并且使用request域对象传递数据的方法,还可以实现请求转发和请求包含。HttpServletResponse和HttpServletRequest在Web开发中至关重要,要认真学习,牢固掌握。 习题 1. 简述请求转发与重定向的异同(至少写3点)。 2. 请编写一个类,该类能够实现访问完App应用下的Servlet后,5s后跳转到index.jsp页面。 3. 编写一个系统登录界面,登录成功跳转到welcome.jsp,否则提示错误并跳转到login.jsp页面。