創(chuàng)建響應(yīng)正文:
·getOutputStream與getWriter方法
·與getWriter方法相關(guān)的一些小疑問
·輸出緩沖區(qū)
·實現(xiàn)動態(tài)文件內(nèi)容的下載
·圖像訪問計數(shù)器
getOutputStream與getWriter方法:
·getOutputStream方法用于返回Servlet引擎創(chuàng)建的字節(jié)輸出流對象,Servlet程序可以按字節(jié)形式輸出響應(yīng)正文。
·getWriter方法用于返回Servlet引擎創(chuàng)建的字符輸出流對象,Servlet程序可以按字符形式輸出響應(yīng)正文。
·getOutputStream和getWriter這兩個方法互相排斥,調(diào)用了其中的任何一個方法后,就不能再調(diào)用另一方法。
·getOutputStream方法返回的字節(jié)輸出流對象的類型為ServletOutputStream,它可以直接輸出字節(jié)數(shù)組中的二進制數(shù)據(jù)。
·getWriter方法將Servlet引擎的數(shù)據(jù)緩沖區(qū)包裝成PrintWriter類型的字符輸出流對象后返回,PrintWriter對象可以直接輸出字符文本內(nèi)容。
·Servlet程序向ServletOutputStream或PrintWriter對象中寫入的數(shù)據(jù)將被Servlet引擎獲取,Servlet引擎將這些數(shù)據(jù)當作響應(yīng)消息的正文,然后再與響應(yīng)狀態(tài)行和各響應(yīng)頭組合后輸出到客戶端。
·Serlvet的service方法結(jié)束后,Servlet引擎將檢查getWriter或getOutputStream方法返回的輸出流對象是否已經(jīng)調(diào)用過close方法,如果沒有,Servlet引擎將調(diào)用close方法關(guān)閉該輸出流對象。
選擇getOutputStream和getWriter方法的要點:
·PrintWriter對象輸出字符文本內(nèi)容時,它內(nèi)部還是將字符串轉(zhuǎn)換成了某種字符集編碼的字節(jié)數(shù)組后再進行輸出,使用PrintWriter對象的好處就是不用編程人員自己來完成字符串到字節(jié)數(shù)組的轉(zhuǎn)換。
·使用ServletOutputStream對象也能輸出內(nèi)容全為文本字符的網(wǎng)頁文檔,但是,如果網(wǎng)頁文檔內(nèi)容是在Servlet程序內(nèi)部使用文本字符串動態(tài)拼湊和創(chuàng)建出來的,則需要先將字符文本轉(zhuǎn)換成字節(jié)數(shù)組后輸出。
·如果一個網(wǎng)頁文檔內(nèi)容全部為字符文本,但是這些內(nèi)容可以直接從一個字節(jié)輸入流中讀取出來,然后再原封不動地輸出到客戶端,那么就應(yīng)該使用ServletOutputStream對象直接進行輸出,而不要使用PrintWriter對象進行輸出。
輸出緩沖區(qū):
·Servlet程序輸出的HTTP消息的響應(yīng)正文首先被寫入到Servlet引擎提供的一個輸出緩沖區(qū)中,直到輸出緩沖區(qū)被填滿或者Servlet程序已經(jīng)寫入了所有的響應(yīng)內(nèi)容,緩沖區(qū)中的內(nèi)容才會被Servlet引擎發(fā)送到客戶端。
·使用輸出緩沖區(qū)后,Servlet引擎就可以將響應(yīng)狀態(tài)行、各響應(yīng)頭和響應(yīng)正文嚴格按照HTTP消息的位置順序進行調(diào)整后再輸出到客戶端。
·如果在提交響應(yīng)到客戶端時,輸出緩沖區(qū)中已經(jīng)裝入了所有的響應(yīng)內(nèi)容,Servlet引擎將計算響應(yīng)正文部分的大小并自動設(shè)置Content-Length頭字段。
·如果在提交響應(yīng)到客戶端時,輸出緩沖區(qū)中裝入的內(nèi)容只是全部響應(yīng)內(nèi)容的一部分, Servlet引擎將使用HTTP 1.1的chunked編碼方式(通過設(shè)置Transfer-Encoding頭字段來指定)傳輸響應(yīng)內(nèi)容。
輸出緩沖區(qū)-有關(guān)方法:
·setBufferSize方法
·getBufferSize方法
·flushBuffer方法
·reset方法
·isCommitted方法
什么是動態(tài)文件內(nèi)容的下載:
·只要讓超鏈接的URL地址指向一個exe或zip等類型的文件,用戶單擊這個超鏈接就可以將該資源文件下載到客戶端。
·如果要下載的文件并不真正存在于WEB服務(wù)器的文件系統(tǒng)中,而是需要用一個Servlet程序臨時在服務(wù)器內(nèi)存中動態(tài)產(chǎn)生后再傳送到客戶端,那該如何實現(xiàn)呢?
如何實現(xiàn)動態(tài)文件內(nèi)容的下載:
·需要通過HttpServletResponse.setContentType方法設(shè)置Content-Type頭字段的值為瀏覽器無法使用某種方式或激活某個程序來處理的MIME類型,例如,“application/octet-stream”或“application/x-msdownload”等。
·需要通過HttpServletResponse.setHeader方法設(shè)置Content-Disposition頭的值為“attachment; filename =文件名”。
·應(yīng)該調(diào)用HttpServletResponse.getOutputStream方法返回的ServletOutputStream對象來向客戶端寫入附件文件內(nèi)容,而不應(yīng)使用HttpServletResponse.getWriter方法返回的PrintWriter對象。
圖像訪問計數(shù)器-介紹:
·網(wǎng)頁每次被訪問時,頁面的訪問次數(shù)都要發(fā)生改變,所以這個功能必須通過服務(wù)器端的程序來實現(xiàn)。
·一些WEB站點只能輸出靜態(tài)頁面內(nèi)容,沒有開放運行服務(wù)器端程序的功能,無法直接在這些只支持靜態(tài)內(nèi)容的WEB站點上編寫服務(wù)器端程序來實現(xiàn)頁面訪問次數(shù)的統(tǒng)計和顯示功能。
·一些具有執(zhí)行服務(wù)器端程序功能的WEB站點推出了免費的頁面訪問計數(shù)器,只要在位于任何站點的一個靜態(tài)HTML頁面中增加一條該站點提供的HTML語句,該語句就能顯示出該靜態(tài)頁面的訪問次數(shù)。
·一個站點要想能統(tǒng)計另外一個站點上的某個HTML頁面的訪問次數(shù),必須讓任何一個瀏覽器在每次訪問那個HTML頁面都通知這個一下站點,這可以通過在靜態(tài)HTML頁面中增加兩種特殊的標簽來實現(xiàn):<img>標簽和設(shè)置src屬性的<script>標簽。
<img>標簽的三個重要特性:
·一個包含有圖像的網(wǎng)頁文件中并沒有包含真正的圖像數(shù)據(jù)內(nèi)容,而只是使用<img>標簽指明了圖像的URL地址。
舉例:本網(wǎng)頁已被瀏覽了<img src= "count.gif ">次
·<img>標簽的src屬性也可以指向當前頁面所在WEB服務(wù)器之外的其他WEB服務(wù)器上的圖像文件。
·瀏覽器并不關(guān)心<img>標簽所需的圖像數(shù)據(jù)在服務(wù)器端是如何產(chǎn)生,它只知道去訪問src屬性指定的URL資源,并把服務(wù)器返回的數(shù)據(jù)當作一個圖像的內(nèi)容來顯示。服務(wù)器返回的圖像數(shù)據(jù)可以直接從一個靜態(tài)圖像文件中讀取,也可以通過Servlet程序在內(nèi)存中動態(tài)創(chuàng)建。
頁面訪問計數(shù)器的技術(shù)實現(xiàn)細節(jié):
·Servlet程序輸出的圖像格式為jpeg,它應(yīng)告訴瀏覽器其所輸出的實體內(nèi)容的MIME類型為image/jpeg。
·因為圖像是二進制數(shù)據(jù),所以應(yīng)該調(diào)用HttpServletResponse.getOutputStream方法返回的ServletOutputStream對象來向客戶端寫入圖像數(shù)據(jù)。
·java.awt.image.BufferedImage類用于在內(nèi)存中創(chuàng)建一幅圖像,具體的圖像內(nèi)容則可以通過調(diào)用其圖形上下文對象(java.awt.Graphics)的各種繪圖方法生成。
·在內(nèi)存圖像中繪制訪問次數(shù)時,必須限定顯示的位數(shù),如果訪問次數(shù)超過七位,則用數(shù)字9999999顯示,如果訪問次數(shù)不足七位,則在前面補充相應(yīng)個數(shù)的0。
·每個引用該Servlet程序的靜態(tài)頁面的URL都對應(yīng)一個各自的訪問次數(shù),每個URL及其訪問次數(shù)需要使用數(shù)據(jù)庫系統(tǒng)來進行存儲,對于簡單的實驗,也可以采用一個屬性文件來進行存儲。當前引用頁面的URL可以通過Referer請求頭獲取。
·JDK中提供了一個javax.imageio.ImageIO類,它的write方法可以將BufferedImage對象中的圖像編碼成jpeg格式的圖像數(shù)據(jù)后寫入到一個OutputStream流對象中。
圖像訪問計數(shù)器-更多思考:
·如果要自行設(shè)置Content-Length頭字段,該如何處理?
·在實際應(yīng)用中,往往采用為每個頁面分配一個id號的方式來區(qū)分和跟蹤每個靜態(tài)HTML頁面,請編寫一個具體應(yīng)用案例。
<img src=http://主機地址:8080/it315/CountServlet?id=本頁面的id號>
·借鑒其中的動態(tài)圖像生成技術(shù),可以根據(jù)數(shù)據(jù)庫系統(tǒng)中的數(shù)據(jù)動態(tài)產(chǎn)生出的各類數(shù)據(jù)分析圖(直方圖、餅狀圖、折線圖等),甚至是股票走勢圖。
·使用設(shè)置src屬性的<script>標簽也可以實現(xiàn)統(tǒng)計和顯示頁面訪問次數(shù)的功能,請編寫一個具體應(yīng)用案例。
·借鑒網(wǎng)頁訪問計數(shù)器的設(shè)計思想統(tǒng),www.it315.org站點還為其他站點的頁面提供了一個“顯示來訪者的IP地址和地區(qū)信息”的功能,請描述一下其實現(xiàn)過程?
多學兩招:如何動態(tài)產(chǎn)生大小可變的圖像:
·涉及到的類:
java.awt.image.BufferedImage
java.awt.image.ImageIO
java.awt.geom.AffineTransform
java.awt.image.AffineTransformOp
·AffineTransformOp類的filter方法用于完成具體的轉(zhuǎn)換操作:
public final BufferedImage filter(BufferedImage src,BufferedImage dst)
·如果要對一個圖像文件進行轉(zhuǎn)換,可以先調(diào)用ImageIO.read方法從該文件輸入流中讀取圖像數(shù)據(jù)并生成一個BufferedImage對象,然后調(diào)用AffineTransformOp.filter方法進行轉(zhuǎn)換,最后再調(diào)用ImageIO.write方法將轉(zhuǎn)換得到的BufferedImage對象寫入到一個文件輸出流中。
·一個AffineTransform對象定義了一種具體的轉(zhuǎn)換方式,在創(chuàng)建AffineTransformOp對象時,需要為其傳遞一個AffineTransform對象:
public AffineTransformOp(AffineTransform xform,int interpolationType)
·如果僅僅是需要改變圖像的大小,可以調(diào)用AffineTransform.getScaleInstance這個靜態(tài)方法來創(chuàng)建:
public static AffineTransform getScaleInstance(double sx, double sy)
請求重定向與請求轉(zhuǎn)發(fā):
·RequestDispatcher接口
·用include方法實現(xiàn)資源包含
·用forward方法實現(xiàn)請求轉(zhuǎn)發(fā)
·請求轉(zhuǎn)發(fā)的運行流程
·用sendRedirect方法實現(xiàn)請求重定向
·請求重定向的運行流程
·請求重定向與請求轉(zhuǎn)發(fā)的比較
·缺省Servlet的緩存問題
RequestDispatcher接口:
·RequestDispatcher實例對象是由Servlet引擎創(chuàng)建的,它用于包裝一個要被其他資源調(diào)用的資源(例如,Servlet、HTML文件、JSP文件等),并可以通過其中的方法將客戶端的請求轉(zhuǎn)發(fā)給所包裝的資源。
·RequestDispatcher接口中定義了兩個方法:forward方法和include方法。
·forward和include方法接收的兩個參數(shù)必須是傳遞給當前Servlet的service方法的那兩個ServletRequest和ServletResponse對象,或者是對它們進行了包裝的ServletRequestWrapper 或ServletResponseWrapper對象。
·獲取RequestDispatcher對象的方法:
üServletContext.getRequestDispatcher (參數(shù)只能是以“/”開頭的路徑)
üServletContext.getNamedDispatcher
üServletRequest.getRequestDispatcher (參數(shù)可以是不以“/”開頭的路徑)
用include方法實現(xiàn)資源包含:
·RequestDispatcher.include方法用于將RequestDispatcher對象封裝的資源內(nèi)容作為當前響應(yīng)內(nèi)容的一部分包含進來,從而實現(xiàn)可編程的服務(wù)器端包含功能。
·被包含的Servlet程序不能改變響應(yīng)消息的狀態(tài)碼和響應(yīng)頭,如果它里面存在這樣的語句,這些語句的執(zhí)行結(jié)果將被忽略。
·在調(diào)用RequestDispatcher.include方法時,Servlet容器不會去調(diào)整HttpServletRequest對象中的信息,HttpServletRequest對象仍然保持其初始的URL路徑和參數(shù)信息。
缺省Servlet如何創(chuàng)建響應(yīng)正文:
ServletOutputStream ostream = null;
PrintWriter writer = null;
……
try
{
/*
如果拋出了異常,說明前面已經(jīng)調(diào)用過getWriter方法,
則在異常處理代碼中再次調(diào)用getWriter方法對writer變量進行賦值。
*/
ostream = response.getOutputStream();
}
catch (IllegalStateException e)
{
/*只有那些文本內(nèi)容才可以用
PrintWriter對象進行轉(zhuǎn)換輸出*/
if ( (contentType == null) ||
(contentType.startsWith("text")) )
{
writer = response.getWriter();
}
else
{
throw e;
}
}
……
//如果已經(jīng)對writer變量賦值,則執(zhí)行else從句
if (ostream != null)
{
//將資源中的內(nèi)容按字節(jié)流原封不動地輸出到客戶端
copy(cacheEntry, renderResult, ostream);
}
else
{
/*將資源中的內(nèi)容轉(zhuǎn)換成字符文本后
再由PrintWriter對象轉(zhuǎn)換輸出*/
copy(cacheEntry, renderResult, writer);
}
用forward方法實現(xiàn)請求轉(zhuǎn)發(fā):
lforward方法用于將請求轉(zhuǎn)發(fā)到RequestDispatcher對象封裝的資源,Servlet程序在調(diào)用這個方法進行轉(zhuǎn)發(fā)之前可以對請求進行一些前期的預處理。
l如果在調(diào)用forward方法之前,在Servlet程序中寫入的部分內(nèi)容已經(jīng)被真正地傳送到了客戶端,forward方法將拋出IllegalStateException異常。
l調(diào)用RequestDispatcher.forward方法時,Servlet容器將根據(jù)目標資源路徑對當前HttpServletRequest對象中的請求路徑和參數(shù)信息進行調(diào)整。
l如果在調(diào)用forward方法之前向Servlet引擎的緩沖區(qū)中寫入了內(nèi)容,只要寫入到緩沖區(qū)中的內(nèi)容還沒有被真正輸出到客戶端,forward方法就可以被正常執(zhí)行,原來寫入到輸出緩沖區(qū)中的內(nèi)容將被清空,但是,已寫入到HttpServletResponse對象中的響應(yīng)頭字段信息保持有效。
l如果調(diào)用者與被調(diào)用者的訪問URL不屬于同一個目錄,當被調(diào)用者輸出的內(nèi)容中包含有使用相對URL的訪問路徑時,原來相對被調(diào)用者的URL將變成相對于調(diào)用者的URL。
用sendRedirect方法實現(xiàn)請求重定向:
lsendRedirect方法用于生成302響應(yīng)碼和Location響應(yīng)頭,從而通知客戶端去重新訪問Location響應(yīng)頭中指定的URL,其完整的定義語法如下:
public void sendRedirect(java.lang.String location)
throws java.io.IOException
l使用下面兩條語句也能完成response.sendRedirect(url)語句所完成的功能:
response.setStatus(response.SC_MOVED_TEMPORARILY );
response.setHeader ("Location", url);
lsendRedirect 方法不僅可以重定向到當前應(yīng)用程序中的其他資源,它還可以重定向到同一個站點上的其他應(yīng)用程序中的資源,甚至是使用絕對URL重定向到其他站點的資源。
l如果傳遞給sendRedirect 方法的相對URL以“/”開頭,則是相對于整個WEB站點的根目錄,而不是相對于當前WEB應(yīng)用程序的根目錄。
請求重定向與請求轉(zhuǎn)發(fā)的比較:
·RequestDispatcher.forward方法只能將請求轉(zhuǎn)發(fā)給同一個WEB應(yīng)用中的組件;而HttpServletResponse.sendRedirect 方法還可以重定向到同一個站點上的其他應(yīng)用程序中的資源,甚至是使用絕對URL重定向到其他站點的資源。
·如果傳遞給HttpServletResponse.sendRedirect 方法的相對URL以“/”開頭,它是相對于整個WEB站點的根目錄;如果創(chuàng)建RequestDispatcher對象時指定的相對URL以“/”開頭,它是相對于當前WEB應(yīng)用程序的根目錄。
·調(diào)用HttpServletResponse.sendRedirect方法重定向的訪問過程結(jié)束后,瀏覽器地址欄中顯示的URL會發(fā)生改變,由初始的URL地址變成重定向的目標URL;調(diào)用RequestDispatcher.forward 方法的請求轉(zhuǎn)發(fā)過程結(jié)束后,瀏覽器地址欄保持初始的URL地址不變。
·HttpServletResponse.sendRedirect方法對瀏覽器的請求直接作出響應(yīng),響應(yīng)的結(jié)果就是告訴瀏覽器去重新發(fā)出對另外一個URL的訪問請求;RequestDispatcher.forward方法在服務(wù)器端內(nèi)部將請求轉(zhuǎn)發(fā)給另外一個資源,瀏覽器只知道發(fā)出了請求并得到了響應(yīng)結(jié)果,并不知道在服務(wù)器程序內(nèi)部發(fā)生了轉(zhuǎn)發(fā)行為。
·RequestDispatcher.forward方法的調(diào)用者與被調(diào)用者之間共享相同的request對象和response對象,它們屬于同一個訪問請求和響應(yīng)過程;而HttpServletResponse.sendRedirect方法調(diào)用者與被調(diào)用者使用各自的request對象和response對象,它們屬于兩個獨立的訪問請求和響應(yīng)過程。
·無論是RequestDispatcher.forward方法,還是HttpServletResponse.sendRedirect方法,在調(diào)用它們之前,都不能有內(nèi)容已經(jīng)被實際輸出到了客戶端。如果緩沖區(qū)中已經(jīng)有了一些內(nèi)容,這些內(nèi)容將被從緩沖區(qū)中清除。
|