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