一、准备实验环境
二、HttpServletResponse简介
- HttpServletResponse是专用于HTTP协议的ServletResponse子接口,它用于封装HTTP响应消息,
允许操控HTTP协议相关数据,包括响应头和状态码,支持Cookies和session跟踪。
HttpServletResponse也定义了一系列用于描述各种HTTP状态码的常量。
三、产生响应状态行
- HTTP响应消息的响应状态可分为三个部分:HTTP版本、状态代码和一条相关的提示信息:如下所示
HTTP/1.1 200 OK - 状态码常量
HttpServletResponse中定义了若干与状态码数值对应的常量,每个常量的名称
以前缀SC(Status Code的简写)开头,然后是状态码在HTTP/1.1规范中所表示的状态信息的
英文单词的组合,每个单词之间用下划线连接,且所有字母都大写。 - setStatus
在使用setStatus方法产生HTTP消息的响应状态行时,只需设置响应状态代码就足够了。
setStatus方法接受一个int类型的参数,这个参数就是要设置的响应状态码。
只有在HTTP响应消息中使用一个特殊的状态码,才需要调用setStatus。 - sendError方法
HttpServletResponse中提供了两个重载的sendError方法,定义如下:
public void sendError(int code) throws java.io.IOException
public void sendError(int code, String message) throws java.io.IOException
四、构建响应消息头
- addHeader与setHeader
addHeader和setHeader方法都可以用于设置HTTP响应消息的各种头字段,它们的语法定义如下:
public void addHeader(java.lang.String name, java.lang.String value)
public void setHeader(java.lang.String name, java.lang.String value) - addIntHeader与setIntHeader方法
HttpServletResponse提供了两个专门用于设置包含整数值的响应头的方法,它们的定义语法为:
public void addIntHeader(java.lang.String name, int value)
public void setIntHeader(java.lang.String name, int value) - addDateHeader与setDateHeader方法
HttpServletResponse提供了两个专门用于设置包含日期值的响应头的方法,它们的定义语法为:
public void addDateHeader(java.lang.String name, long value)
public void setDateHeader(java.lang.String name, long value) - setContentLength方法
setContentLength方法用于设置响应消息的实体内容的大小,单位为字节。对于HTTP协议来说,
这个方法就是设置Content-Length响应头字段的值。
因为浏览器与Web服务器之间使用持久(keep-alive)的HTTP连接,如果Web服务器没有采用
chunked传输编码方式,那么它必须在每个应答中发送一个content-Length的响应头来表示
各个实体内容的长度,以便客户端能够分辨出上一个响应内容的结束位置。 - setContentType方法
setContentType方法用于设置Servlet输出内容的MIME类型,对于HTTP协议来说,
就是设置Content-Type响应头字段的值。
在Servlet程序中需要显式地调用setContentType方法来对此进行制定。在MIME类型后面还可以
指定响应内容所使用的字符集类型。如"text/html; charset=GB2312",默认ISO8859-1。 - setCharacterEncoding方法
setCharacterEncoding方法用于设置输出内容的MIME声明中的字符集编码,
对HTTP协议来说,就是设置Content-Type头字段中的字符集编码部分。
如果没有设置Content-Type头字段,setCharacterEncoding方法设置的字符集编码
不会出现在HTTP消息的响应头中,但是它的设置结果仍然决定了ServletResponse.getWriter
方法返回的PrintWriter对象输出文本内容时所采用的字符集编码。
setCharacterEncoding方法比setContentType和setLocale方法的优先级高。 - setLocale方法
setLocale方法用于设置响应消息的本地化信息,对HTTP来说,它将设置Content-Language
响应头字段和Content-Type头字段中的字符集编码部分。
如果没有设置Content-Type头字段,setLocale方法设置的字符集编码
不会出现在HTTP消息的响应头中,但是它的设置结果仍然决定了ServletResponse.getWriter
方法返回的PrintWriter对象输出文本内容时所采用的字符集编码。
语法定义如下:
public void setLocale(java.util.Locale loc)
在部署描述符文件(web.xml)中可以通过<locale-encoding-mapping-list>元素
来设置各种Locale与字符集编码之间的映射关系,如下所示:
<locale-encoding-mapping-list>
<locale-encoding-mapping>
<locale>zh_CH</locale>
<encoding>GB2312</encoding>
</locale-encoding-mapping>
</locale-encoding-mapping-list> - containsHeader方法
containsHeader方法用于检查某个名称的头字段是否已经被设置。
五、响应消息头的实用案例
- Servlet的中文输出问题
Servlet程序输出的中文字符在浏览器中显示为乱码的原因有两种可能:
Servlet程序输出给浏览器的内容不是任何一种正确的中文字符集编码;
浏览器显示网页文档时所采用的字符集编码
与它所接收到的中文字符本身的字符集编码不一致。 - 让浏览器定时刷新网页或跳转至其他网页
HTTP协议中定义了一个Reflesh头字段,用于告诉浏览器过多少秒后字段刷新页面。
在Reflesh头字段的时间设置值后面还可以用分号(;)分隔后,再指定一个URL地址,
这将让浏览器在指定的时间值后自动去访问该URL地址指向的资源。 - 禁止浏览器缓存当前文档内容
有三个HTTP响应头字段都可以禁止浏览器缓存当前页面,它们在Servlet程序中的实例代码分别如下:
response.setDataHeader("Expires", 0)
response.setHeader("Cache-Control", "no-cache")
response.setHeader("Pragma", "no-cache") - 使用<meta>标签模拟响应消息头
HTML语言中专门定义了<meta>标签的equiv属性来在HTML文档中模拟HTTP响应消息头,
当浏览器读取到HTML文档中具有http-equiv属性的<meta>标签时,
它会用于处理Web服务器发送的响应消息头一样的方式来进行处理。
例如,如果不想让浏览器缓存一个内容经常要被更新的静态HTML页面,
那么可以在它的<head></head>标签对之间增加如下三条<meta>语句:
<meta http-equiv="Expires" content="0">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Pragma" content="no-cache">
例如,在某个网站的正文部分中增加如下的一条语句:
<meta http-equiv="Refresh" content="0;url=http://www.xxx.com">
例如,对于静态的HTML页面,可以通过将<meta>标签的http-equiv属性设置为“Content-Type”:
<meta http-equiv="Content-Type" content="text/html; charset=GB2312">
所以,在每个静态的中文HTML页面的开始处都应增加一条<meta>语句来设置页面的字符集编码,
已经成为了设计HTML页面的指导性原则和验收标准。
实验证明,如果网页文档采用UTF-8编码,那么,浏览器能够自动进行识别,不会再按照Content-TYpe
头字段中指定的字符集编码来显示当前网页文档,而是始终采用UTF-8编码来显示。
六、创建响应正文
- getOutputStream与getWriter
getOutputStream方法返回的字节输出流对象的类型为ServletOutputStream。
getWriter方法将Servlet引擎的数据缓冲区包装成PrintWriter类型的字符输出流对象后返回。
Servlet程序向ServletOutputStream或PrintWriter对象中写入的数据
将被Servlet引擎获取,Servlet引擎将这些数据当做响应消息的正文,
然后再与响应状态行和各种响应头组合后输出到客户端。
Servlet引擎会自动调用close方法关闭该输出流对象。开发人员最好自己close。 - 关于getWriter方法的一些相关疑问
每次调用getWriter方法返回的PrintWriter对象的
引用变量指向的是同一个PrintWriter对象。
PrintWriter.println方法与PrintWriter.print方法的区别在于
它们输出到客户端的内容后面是否有换行,但浏览器显示不出这个换行,
这个换行主要是为了增强浏览器接收到的源文件的易读性和易理解性。 - 输出缓冲区
如果在提交响应到客户端时,输出缓冲区中已经装入了所有的响应内容,
Servlet引擎将计算响应正文部分的大小并自动设置Content-Length头字段。
如果在提交响应到客户端时,输出缓冲区中装入的内容只是全部响应内容的一部分,
那么Servlet引擎将无法再计算Content-Length头字段的值,
它将使用HTTP1.1的chunked编码方式(通过设置Transfer-Encoding头字段来指定)
传输响应内容,这样就不用设置Content-Length头字段了。
1、setBufferSize方法
setBufferSize方法用于设置期望的输出缓冲区大小。
2、getBufferSize方法
getBufferSize方法用于返回Servlet引擎实际使用的缓冲区大小。
3、flushBuffer方法
flushBuffer方法用于将输出缓冲区的内容强制输出到客户端。
4、reset方法
reset方法用于清空输出缓冲区中的内容,以及设置的响应状态码和各个响应头。
5、isCommitted方法
isCommitted方法判断是否已经提交了部分响应内容到客户端,
如果已提交了,则返回true,否则,返回false。 - 实现动态文件内容的下载
需要调用HttpServletResponse.setContentType方法,将Content-Type头字段的值
设置为浏览器无法直接打开的MIME类型,或者无法激活某个程序来进行处理得MIME类型,
例如,“application/octet-stream”或“application/x-msdownload”等。
需要通过HttpServletResponse.setHeader方法设置
Content-Disposition头的值为“attachment:filename=文件名”。
因为附件文件可以是各种类型的文件,要将附件文件中的内容传送给客户端,其中的内容应该
被当做二进制处理。所以应该使用ServletOutputStream对象来向客户端写入附件文件内容。 - 图像访问计数器
Servlet程序应告诉浏览器其所输出的实体内容的MIME类型为image/jpeg。
应该使用ServletOutputStream对象来向客户端写入图像数据。
java.awt.image.BufferedImage类用于在内存中创建一副图像,具体的图像内容则可以
通过调用其图像上下文对象(java.awt.Graphics)的各种绘图方式生成。
在内存图像中绘制访问次数时,必须限定显示的位数,如果访问次数超过七位,
则用数字9999999显示,如果访问次数不足七位,则在前面补充相应个数的0。
每个应用该Servlet程序的静态页面的URL都对应一个各自的访问次数,
每个URL及其访问次数需要使用数据库系统来进行存储。
对于简单的,也可以采用一个属性文件来进行存储。
JDK中提供了一个javax.imageio.ImageIO类,只需要调用imageIO.write方法
将BufferedImage对象中的图像编码成jpeg格式后写入到ServletOutputStream流对象中。
七、请求重定向与请求转发
- 在Servlet程序中,有时需要调用另外一个资源来对浏览器的请求进行响应,这可以通过
两种方式来实现:其中一种就是调用RequestDispatcher.forward方法实现的请求转发;
另外一种则是调用HttpServletResponse.sendRedirect方法实现的请求重定向。 - RequestDispatcher接口
ServletAPI中定义了一个RequestDispatcher接口,它定义了RequestDispatcher
实例对象的方法。RequestDispatcher实例对象是由Servlet引擎创建的,
它用于包装一个要被其他资源调用的资源,并可以通过其中的方法
将客户端的请求转发给所包装的资源。
RequestDispatcher接口中定义了两个方法:forward方法和include方法,
它们分别用于将请求转发到RequestDispatcher对象封装的资源和
将RequestDispatcher对象封装的资源作为当前响应内容的一部分包含进来。
ServletContext接口中定义了两个用于获取RequestDispatcher对象的方法:
getRequestDispatcher方法
传递给方法的路径字符串必须以“/”开头,
这个“/”代表的是当前Web应用程序的根目录。
getNamedDispatcher方法
在ServletRequest接口中也定义了一个getRequestDispatcher方法来获得
RequestDispatcher对象。它与ServletContext.getRequestDispatcher
方法的区别在于:传递给这个方法的参数除了可以采用“/”开头的路径字符串,
还可以采用非“/”开头的相对路径。
RequestDispatcher对象只能包装当前Web应用程序中的资源,所以,forward方法和
include方法只能在同一个Web应用程序内的资源之间转发请求和实现资源包含。 - 用include方法实现资源包含
RequestDispatcher.include方法用于将RequestDispatcher对象封装的资源内容作为
当前响应内容的一部分包含进来,从而实现可编程的服务端包含功能。
被包含的Servlet程序不能改变响应消息的状态码和响应头,
如果它里面存在这样的语句,这些语句的执行结果将被忽略。
在调用RequestDispatcher.include方法时,Servlet容器不会去调整HttpServletRequest
对象中的信息,HttpServletRequest对象仍然保持其初始的URL路径和参数信息。 - 用forward方法实现请求转发
forward方法用于将请求转发到RequestDispatcher对象封装的资源,Servlet程序
在调用这个方法进行转发之前可以对请求进行一些前期的预处理。
使用forward方法时,应注意下面一些问题:
(1)如果在调用forwawrd方法之前,在Servlet程序中写入的部分内容已经被
真正地传送到了客户端,forward方法将抛出IllegalStateException异常。
(2)调用RequestDispatcher.forward方法时,Servlet容器将根据目标资源路径
对当前HttpServletRequest对象中的请求路径和参数信息进行调整。
(3)如果在调用forward方法之前向Servlet引擎的缓冲区中写入了内容,只要
写入到缓冲区中的内容还没有被真正输出到客户端,forward方法就可以
被正常执行,原来写入到输出缓冲区中的内容将被清空。
在调用forward方法之后,如果调用者程序继续向Servlet引擎的缓冲区中
执行写入操作,这些写入操作的执行结果将被忽略。
(4)在调用者程序中设置的响应状态码和响应头不会被忽略,
在被调用程序中设置的响应状态码和响应头也不会被忽略。
(5)如果调用者与被调用者的URL不属于同一个目录,当被调用者输出的内容中包含
使用相对URL的访问路径时,原来相对被调用者的URL将变成相对于调用者的URL。 - 请求转发的运行流程
- 用sendRedirect方法实现请求重定向
sendRedirect方法用于生成302响应码和Location响应头,从而通知客户端去重新访问
Location响应头中指定的URL,其完整的定义语法如下:
public void sendRedirect(String location) throws IOException
其中的location参数指定了重定向的URL,它可以使用相对URL,Servlet
引擎会自动将相对URL翻译成绝对URL后,再生成Location头字段。
使用下面两条语句也能完成response.sendRedirect(url)语句所完成的功能:
response.setStatus(response.SC_MOVED_TEMPORARILY)
response.setHeader("Location", url)
sendRedirect方法不仅可以重定向到当前应用程序中的其他资源,
它还可以重定向到同一个站点上的其他应用程序中的资源,
甚至是使用绝对URL重定向到其他站点的资源。
如果传递给sendRedirect方法的相对URL不是以“/”开头,则表示是相对于当前请求的URL;
如果该相对URL是以“/”开头,则是相对于整个Web站点的根目录,
而不是相对于当前Web应用程序的根目录。
调用sendRedirect方法的语句与调用普通方法的语句的执行流程是完全一样的,
位于这个语句后面的程序代码仍然会被执行,只是向Servlet引擎的
缓冲区中进行写入的那些语句的执行结果将被忽略。 - 请求重定向的运行流程
- HttpServletResponse.sendRedirect方法实现的请求重定向
与RequestDispatcher.forward方法实现的请求转发的总结比较:
(1)RequestDispatcher.forward方法只能将请求转发给同一个Web应用中的组件;
而HttpServletResponse.sendRedirect方法不仅可以重定向到当前应用程序中的
其他资源,还可以重定向到一个站点上的其他应用程序中的资源,甚至是使用绝对URL
重定向到其他站点的资源。如果传递给HttpServletResponse.sendRedirect方法的相对
URL以“/”开头,它是相对于整个Web站点的根目录;如果创建RequestDispatcher对象时
指定的相对URL以“/”开头,它是相对于当前Web应用程序的根目录。
(2)调用HttpServletResponse.sendRedirect方法重定向的访问过程结束后,
浏览器的地址栏中显示的URL会发生变化,由初始的URL地址变成重定向的目标URL;
而调用RequestDispatcher.forward方法的请求转发过程结束后,
浏览器地址栏保持初始的URL地址不变。
(3)HttpServletResponse.sendRedirect方法对浏览器的请求直接做出响应,
响应的结果就是告诉浏览器去重新发出对另外一个URL的访问请求,
RequestDispatcher.forward方法在服务器端内部将请求转发给另外一个资源,浏览器
只知道发出了请求并得到了响应结果,并不知道在服务器程序内部发生了转发行为。
(4)RequestDispatcher.forward方法的调用者与被调用者之间共享相同的
request对象和response对象,它们属于同一个访问请求和响应过程;
而HttpServletResponse.sendRedirect方法调用者与被调用者使用各自的
request对象和response对象,它们属于两个独立的访问请求和响应过程。
如果一次请求的响应过程被设计成Web应用程序内部的多个资源来协同完成,
这种情况下就必须使用RequestDispatcher.forward方法。
不同Web应用程序之间的重定向,特别是要重定向到另外一个Web站点上的资源的情况,
都应该使用HttpServletResponse.sendRedirect方法。
(5)无论是RequestDispatcher.forward方法,
还是HttpServletResponse.sendRedirect方法,在调用它们之前,都不能有内容已经被
实际输出到了客户端。如果缓冲区中已经有了一些内容,这些内容将被从缓冲区中清除。 - 缺省Servlet的缓存问题
客户端对静态HTML文件和图片的访问其实都是由缺省Servlet来完成响应的,
访问HTML文件实际上就是在调用缺省Servlet。该缺省Servlet有缓存5秒。