创建响应正文:
•getOutputStream与getWriter方法•与getWriter方法相关的一些小疑问•输出缓冲区•实现动态文件内容的下载•图像访问计数器
getOutputStream与getWriter方法:
• getOutputStream 方法用于返回 Servlet 引擎创建的字节输出流对象, Servlet 程序可以按字节形式输出响应正文。• getWriter 方法用于返回 Servlet 引擎创建的字符输出流对象, Servlet 程序可 以按字符形式输出响应正文。• getOutputStream 和 getWriter 这两个方法互相排斥,调用了其中的任何一个 方法后,就不能再调用另一方法。• getOutputStream 方法返回的字节输出流对象的类型为 ServletOutputStream ,它可以直接输出字节数组中的二进制数据。• getWriter 方法将 Servlet 引擎的数据缓冲区包装成 PrintWriter 类型的字符输出 流对象后返回, PrintWriter 对象可以直接输出字符文本内容。• Servlet 程序向 ServletOutputStream 或 PrintWriter 对象中写入的数据将被 Servlet 引擎获取, Servlet 引擎将这些数据当作响应消息的正文,然后再与 响应状态行和各响应头组合后输出到客户端。• Serlvet 的 service 方法结束后, Servlet 引擎将检查 getWriter 或 getOutputStream 方法返回的输出流对象是否已经调用过 close 方法,如果没 有, Servlet 引擎将调用 close 方法关闭该输出流对象。
选择getOutputStream和getWriter方法的要点:
• PrintWriter 对象输出字符文本内容时,它内部还是将字符串转 换成了某种字符集编码的字节数组后再进行输出,使用 PrintWriter 对象的好处就是不用编程人员自己来完成字符串到 字节数组的转换。• 使用 ServletOutputStream 对象也能输出内容全为文本字符的网 页文档,但是,如果网页文档内容是在 Servlet 程序内部使用文 本字符串动态拼凑和创建出来的,则需要先将字符文本转换成 字节数组后输出。• 如果一个网页文档内容全部为字符文本,但是这些内容可以直 接从一个字节输入流中读取出来,然后再原封不动地输出到客 户端,那么就应该使用 ServletOutputStream 对象直接进行输 出,而不要使用 PrintWriter 对象进行输出。
输出缓冲区:
• Servlet 程序输出的 HTTP 消息的响应正文首先被写入到 Servlet 引擎提供的一 个输出缓冲区中,直到输出缓冲区被填满或者 Servlet 程序已经写入了所有 的响应内容,缓冲区中的内容才会被 Servlet 引擎发送到客户端。• 使用输出缓冲区后, Servlet 引擎就可以将响应状态行、各响应头和响应正 文严格按照 HTTP 消息的位置顺序进行调整后再输出到客户端。• 如果在提交响应到客户端时,输出缓冲区中已经装入了所有的响应内容, Servlet 引擎将计算响应正文部分的大小并自动设置 Content-Length 头字段。• 如果在提交响应到客户端时,输出缓冲区中装入的内容只是全部响应内容的 一部分, Servlet 引擎将使用 HTTP 1.1 的 chunked 编码方式(通过设置 Transfer-Encoding 头字段来指定)传输响应内容。
输出缓冲区-有关方法:
• setBufferSize 方法• getBufferSize 方法• flushBuffer 方法• reset 方法• isCommitted 方法
什么是动态文件内容的下载:
• 只要让超链接的 URL 地址指向一个 exe 或 zip 等类型的文件,用 户单击这个超链接就可以将该资源文件下载到客户端。• 如果要下载的文件并不真正存在于 WEB 服务器的文件系统中, 而是需要用一个 Servlet 程序临时在服务器内存中动态产生后再 传送到客户端,那该如何实现呢?
如何实现动态文件内容的下载:
• 需要通过 HttpServletResponse.setContentType 方法设置 Content-Type 头字段的值 为浏览器无法使用某种方式或激活某个程序来处理的 MIME 类型,例如, “ application/octet-stream ” 或 “ application/x-msdownload ” 等。• 需要通过 HttpServletResponse.setHeader 方法设置 Content-Disposition 头的值为 “ attachment; filename = 文件名 ” 。• 应该调用 HttpServletResponse.getOutputStream 方法返回的 ServletOutputStream 对象来向客户端写入附件文件内容,而不应使用 HttpServletResponse.getWriter 方 法返回的 PrintWriter 对象。
图像访问计数器-介绍:
• 网页每次被访问时,页面的访问次数都要发生改变,所以这个功能必须通过 服务器端的程序来实现。• 一些 WEB 站点只能输出静态页面内容,没有开放运行服务器端程序的功能, 无法直接在这些只支持静态内容的 WEB 站点上编写服务器端程序来实现页面 访问次数的统计和显示功能。• 一些具有执行服务器端程序功能的 WEB 站点推出了免费的页面访问计数器, 只要在位于任何站点的一个静态 HTML 页面中增加一条该站点提供的 HTML 语 句,该语句就能显示出该静态页面的访问次数。• 一个站点要想能统计另外一个站点上的某个 HTML 页面的访问次数,必须让任 何一个浏览器在每次访问那个 HTML 页面都通知这个一下站点,这可以通过在 静态 HTML 页面中增加两种特殊的标签来实现: <img> 标签和设置 src 属性的 <script> 标签。
<img>标签的三个重要特性:
• 一个包含有图像的网页文件中并没有包含真正的图像数据内容,而只 是使用 <img> 标签指明了图像的 URL 地址。举例: 本网页已被浏览了 <img src= "count.gif "> 次• <img> 标签的 src 属性也可以指向当前页面所在 WEB 服务器之外的其他 WEB 服务器上的图像文件。• 浏览器并不关心 <img> 标签所需的图像数据在服务器端是如何产生, 它只知道去访问 src 属性指定的 URL 资源,并把服务器返回的数据当作 一个图像的内容来显示。服务器返回的图像数据可以直接从一个静态 图像文件中读取,也可以通过 Servlet 程序在内存中动态创建。
页面访问计数器的技术实现细节:
• Servlet 程序输出的图像格式为 jpeg ,它应告诉浏览器其所输出的实体内容的 MIME 类型为 image/jpeg 。• 因为图像是二进制数据,所以应该调用 HttpServletResponse.getOutputStream 方法返回的 ServletOutputStream 对象来向客户端写入图像数据。• java.awt.image.BufferedImage 类用于在内存中创建一幅图像,具体的图像内容则可以通过调 用其图形上下文对象( java.awt.Graphics )的各种绘图方法生成。• 在内存图像中绘制访问次数时,必须限定显示的位数,如果访问次数超过七位,则用数字 9999999 显示,如果访问次数不足七位,则在前面补充相应个数的 0 。• 每个引用该 Servlet 程序的静态页面的 URL 都对应一个各自的访问次数,每个 URL 及其访问次数 需要使用数据库系统来进行存储,对于简单的实验,也可以采用一个属性文件来进行存储。当 前引用页面的 URL 可以通过 Referer 请求头获取。• JDK 中提供了一个 javax.imageio.ImageIO 类,它的 write 方法可以将 BufferedImage 对象中的图 像编码成 jpeg 格式的图像数据后写入到一个 OutputStream 流对象中。
图像访问计数器-更多思考:
• 如果要自行设置 Content-Length 头字段,该如何处理?• 在实际应用中,往往采用为每个页面分配一个 id 号的方式来区分和跟踪每个 静态 HTML 页面,请编写一个具体应用案例。<img src=http:// 主机地址 :8080/it315/CountServlet?id= 本页面的 id 号 >• 借鉴其中的动态图像生成技术,可以根据数据库系统中的数据动态产生出的 各类数据分析图(直方图、饼状图、折线图等),甚至是股票走势图。• 使用设置 src 属性的 <script> 标签也可以实现统计和显示页面访问次数的功 能,请编写一个具体应用案例。• 借鉴网页访问计数器的设计思想统, www.it315.org 站点还为其他站点的页 面提供了一个 “ 显示来访者的 IP 地址和地区信息 ” 的功能,请描述一下其实 现过程?
多学两招:如何动态产生大小可变的图像:
• 涉及到的类:java.awt.image.BufferedImagejava.awt.image.ImageIOjava.awt.geom.AffineTransformjava.awt.image.AffineTransformOp• AffineTransformOp 类的 filter 方法用于完成具体的转换操作:public final BufferedImage filter(BufferedImage src,BufferedImage dst)•如果要对一个图像文件进行转换,可以先调用ImageIO.read方法从该文件输入流中读取图像数据并生成一个BufferedImage对象,然后调用AffineTransformOp.filter方法进行转换,最后再调用ImageIO.write方法将转换得到的BufferedImage对象写入到一个文件输出流中。•一个AffineTransform对象定义了一种具体的转换方式,在创建AffineTransformOp对象时,需要为其传递一个AffineTransform对象:public AffineTransformOp( AffineTransform xform,int interpolationType)• 如果仅仅是需要改变图像的大小,可以调用 AffineTransform.getScaleInstance 这个静态方法 来创建:public static AffineTransform getScaleInstance(double sx, double sy)
请求重定向与请求转发:
• RequestDispatcher 接口• 用 include 方法实现资源包含• 用 forward 方法实现请求转发• 请求转发的运行流程• 用 sendRedirect 方法实现请求重定向• 请求重定向的运行流程• 请求重定向与请求转发的比较• 缺省 Servlet 的缓存问题
RequestDispatcher接口:
• RequestDispatcher 实例对象是由 Servlet 引擎创建的,它用于包装一个要被 其他资源调用的资源(例如, Servlet 、 HTML 文件、 JSP 文件等),并可以通 过其中的方法将客户端的请求转发给所包装的资源。• RequestDispatcher 接口中定义了两个方法: forward 方法和 include 方法。• forward 和 include 方法接收的两个参数必须是传递给当前 Servlet 的 service 方法的那两个 ServletRequest 和 ServletResponse 对象,或者是对 它们进行了包装的 ServletRequestWrapper 或 ServletResponseWrapper 对 象。• 获取 RequestDispatcher 对象的方法:ü ServletContext.getRequestDispatcher (参数只能是以 “ / ” 开头的路径)ü ServletContext.getNamedDispatcherü ServletRequest.getRequestDispatcher (参数可以是不以 “ / ” 开头的路径)
用include方法实现资源包含:
• RequestDispatcher.include 方法用于将 RequestDispatcher 对象封装的资源内容作 为当前响应内容的一部分包含进来,从而实现可编程的服务器端包含功能。• 被包含的 Servlet 程序不能改变响应消息的状态码和响应头,如果它里面存在这样的 语句,这些语句的执行结果将被忽略。• 在调用 RequestDispatcher.include 方法时, Servlet 容器不会去调整 HttpServletRequest 对象中的信息, HttpServletRequest 对象仍然保持其初始的 URL 路径和参数信息。
缺省Servlet如何创建响应正文:
ServletOutputStream ostream = null;PrintWriter writer = null;……try{/*如果抛出了异常,说明前面已经调用过 getWriter 方法,则在异常处理代码中再次调用 getWriter 方法对 writer 变量进行赋值。*/ostream = response.getOutputStream();}catch (IllegalStateException e){/* 只有那些文本内容才可以用PrintWriter 对象进行转换输出 */if ( (contentType == null) ||(contentType.startsWith("text")) ){writer = response.getWriter();}else{throw e;}}……// 如果已经对 writer 变量赋值,则执行 else 从句if (ostream != null){// 将资源中的内容按字节流原封不动地输出到客户端copy(cacheEntry, renderResult, ostream);}else{/* 将资源中的内容转换成字符文本后再由 PrintWriter 对象转换输出 */copy(cacheEntry, renderResult, writer);}
用forward方法实现请求转发:
lforward方法用于将请求转发到RequestDispatcher对象封装的资源,Servlet程序在调用这个方法进行转发之前可以对请求进行一些前期的预处理。l如果在调用forward方法之前,在Servlet程序中写入的部分内容已经被真正地传送到了客户端,forward方法将抛出IllegalStateException异常。l调用RequestDispatcher.forward方法时,Servlet容器将根据目标资源路径对当前HttpServletRequest对象中的请求路径和参数信息进行调整。l如果在调用forward方法之前向Servlet引擎的缓冲区中写入了内容,只要写入到缓冲区中的内容还没有被真正输出到客户端,forward方法就可以被正常执行,原来写入到输出缓冲区中的内容将被清空,但是,已写入到HttpServletResponse对象中的响应头字段信息保持有效。l如果调用者与被调用者的访问URL不属于同一个目录,当被调用者输出的内容中包含有使用相对URL的访问路径时,原来相对被调用者的URL将变成相对于调用者的URL。
用sendRedirect方法实现请求重定向:
l sendRedirect方法用于生成302响应码和Location响应头,从而通知客户端去重新访问Location响应头中指定的URL,其完整的定义语法如下:public void sendRedirect(java.lang.String location)throws java.io.IOExceptionl 使用下面两条语句也能完成response.sendRedirect(url)语句所完成的功能:response.setStatus(response.SC_MOVED_TEMPORARILY );response.setHeader ("Location", url);l sendRedirect 方法不仅可以重定向到当前应用程序中的其他资源,它还可以重定向到同一个站点上的其他应用程序中的资源,甚至是使用绝对URL重定向到其他站点的资源。l 如果传递给sendRedirect 方法的相对URL以“/”开头,则是相对于整个WEB站点的根目录,而不是相对于当前WEB应用程序的根目录。
请求重定向与请求转发的比较:
• RequestDispatcher.forward 方法只能将请求转发给同一个 WEB 应用中的组件;而 HttpServletResponse.sendRedirect 方法还可以重定向到同一个站点上的其他应用程序中的资源,甚至是 使用绝对 URL 重定向到其他站点的资源。• 如果传递给 HttpServletResponse.sendRedirect 方法的相对 URL 以 “ / ” 开头,它是相对于整个 WEB 站点的根 目录;如果创建 RequestDispatcher 对象时指定的相对 URL 以 “ / ” 开头,它是相对于当前 WEB 应用程序的根目 录。• 调用 HttpServletResponse.sendRedirect 方法重定向的访问过程结束后,浏览器地址栏中显示的 URL 会发生 改变,由初始的 URL 地址变成重定向的目标 URL ;调用 RequestDispatcher.forward 方法的请求转发过程结束 后,浏览器地址栏保持初始的 URL 地址不变。• HttpServletResponse.sendRedirect 方法对浏览器的请求直接作出响应,响应的结果就是告诉浏览器去重新 发出对另外一个 URL 的访问请求; RequestDispatcher.forward 方法在服务器端内部将请求转发给另外一个资 源,浏览器只知道发出了请求并得到了响应结果,并不知道在服务器程序内部发生了转发行为。• RequestDispatcher.forward 方法的调用者与被调用者之间共享相同的 request 对象和 response 对象,它们属 于同一个访问请求和响应过程;而 HttpServletResponse.sendRedirect 方法调用者与被调用者使用各自的 request 对象和 response 对象,它们属于两个独立的访问请求和响应过程。• 无论是 RequestDispatcher.forward 方法,还是 HttpServletResponse.sendRedirect 方法,在调用它们之 前,都不能有内容已经被实际输出到了客户端。如果缓冲区中已经有了一些内容,这些内容将被从缓冲区中 清除。