1. 程式人生 > >[Servlet]HttpServletResponse設定響應標頭、緩衝區、語系編碼、MIME

[Servlet]HttpServletResponse設定響應標頭、緩衝區、語系編碼、MIME

1. 設定響應標頭:

    1) 標頭中的內容也是以鍵值對的形式出現,一行一個鍵值對,格式是"鍵:值列表",標頭允許一個鍵可以有多個值;

    2) 在這裡羅列一下常用的HttpServletResponse裡關於響應標頭設定的方法;

    3) setHeader和addHeader:

         i. void setHeader(String name, String value); // 設定name鍵的值為value,如果不存在將新增,如果存在則覆蓋該鍵的值

         ii. void addHeader(String name, String value); // 給name鍵新增一個值value,允許多值新增,如果name鍵不存在則建立

    4) 如果要新增或者設定的值是整數、日期等特定型別的值則HttpServletResponse也提供了相應型別的方法:

         i. void setIntHeader(String name, int value); // 設定int型鍵值

         ii. void addIntHeader(String name, int value); // 新增int型值

         iii. void setDateHeader(String name, long date); // 設定Date型值

         iv. void addDateHeader(String name, long date); // 新增Date型值

!!很可惜的是HttpServletResponse只多提供了這4個特殊型別值的標頭設定方法,並沒有包裝其它型別值的標頭設定方法,如double等;

    5) 所有標頭的設定都必須在響應確認(Commit)前進行,如果在響應確認後在設定則晚了,會被完全忽略,因為響應已經發送給客戶端了;

!!那什麼是響應確認呢?在接下來的響應緩衝區的介紹中會解釋;

2. 設定響應緩衝區:

    1) 響應緩衝區就相當於C語言使用printf等輸出函式時使用的緩衝區,所有的printf都不會立馬就輸出到螢幕上,而是先進入緩衝區,等到要衝刷(flush)時再將緩衝區中的內容顯示至螢幕;

    2) 響應緩衝區就是PrintWriter的緩衝區,並不是呼叫了PrintWriter的各種輸出函式後立馬就會發送至客戶端,而是先將這些內容暫存在響應緩衝區buffer中,一旦符合了某些條件就會將緩衝區中的內容傳送至客戶端(flush沖刷),具體是什麼條件能觸發沖刷動作後面會具體羅列;

    3) HttpServletResponse操作響應緩衝區的常用方法羅列:

    // 緩衝區大小的獲取和設定

         i. int getBufferSize(); // 返回當前緩衝區的大小,單位是位元組,預設情況下是8192,即8KB大小

         ii. void setBufferSize(int size);  // 直接設定緩衝區的大小

!!setBufferSize必須要在getWriter和getOutputStream之前呼叫,只有這樣才會使獲得的輸出流套用該設定,如果在獲得輸出劉之後再改動緩衝區大小會直接丟擲IllegalStateException異常;

    // 緩衝區內容的清除

         iii. void reset();  // 將緩衝區內的所有內容都清除,包括標頭、狀態碼和body(HTML輸出語句等)全部都清除

         iv. void resetBuffer(); // 只清除body,但保留標頭和狀態碼

!!這兩個函式必須要在響應提交給客戶端(即Commit確認)之前呼叫,如果已經提交了(緩衝已經空了)再清除內容會直接丟擲IllegalStateException;

    // 所以確認是否Commit是關鍵,使用isCommitted來檢視當前是否處於提交狀態

         v. boolean isCommitted();  // 檢視一輪響應是否提交了,一般要在reset和resetBuffer呼叫之前先呼叫這個函式確認響應還未提交

    // 沖刷緩衝區flush

         vi. void flushBuffer();  // 手動強制緩衝區中的內容沖刷至客戶端,即將當前緩衝區中已存在的內容全部提交給客戶端,該動作會直接出發Commit;

    4) 提交的條件:符合以下6個條件都會觸發flush動作提交當前緩衝區中的內容給客戶端

         i. 手動呼叫flushBuffer方法;

         ii. 響應緩衝區的內容已滿會自動沖刷;

!!對於一個Response就只有一個緩衝區,如果所有要提交給客戶端的內容超過緩衝區大小,那麼就會分若干次提交,每次緩衝區滿就會沖刷一次;

         iii. Servlet的service方法結束時會強制沖刷一次緩衝區;

         iv. 響應的內容超過標頭中指定的body長度的上限時會自動沖刷緩衝區,並關閉輸出流(因為標頭中規定body只能有這麼多,超出部分不予以提交);

!!設定響應body大小上限的方法是:void HttpServletResponse.setContentLength(int len); // 單位是位元組,該函式會直接設定Content-Length標頭!!

         v. 呼叫sendRedirect重定向URL時會觸發flush;

         vi. 呼叫sendError向客戶端報錯時會觸發flush;

         vii. 呼叫了AsyncContext的complete方法時會觸發flush;

!!以上三個後面具體會講;

3. 設定語系和編碼:

    1) 使用HttpServletResponse的getWriter返回的PrintWriter輸出時預設的字元編碼是ISO-8859-1,如果不注意瀏覽器識別的編碼和該預設響應編碼不符的話會導致亂碼問題;

    2) 這裡有三種方法來設定響應內容的編碼:設定語系、setCharacterEncoding、setContentType,它們都需要在getWriter之前使用,否則獲取的PrintWriter物件不會套用該設定;

    3) Locale——語系:

         i. Locale是Java SE中定義的類,該類包含兩個資訊,一個是語言,一個是地區,這兩者共同組成Locale(語系);

         ii. 而每種語系都對應著一個預設的字元編碼,JRE內部維護這一個對映表,表中將每一個語系都對映到一個對應的字元編碼方案上,因此一個Locale語系其實包含著三重屬性,就是語言、地區還有字元編碼;

         iii. Locale的構造器:Locale(String language, String country);

!!國際規範要求language和country都是特定的字串,都是兩個字元,並且language要求為小寫,country要求為大寫,比如zh-CN,其中語言zh表示中文(zh即”中文“的”中“字”的拼音的頭兩個字母,而CN則是China的縮寫,即中文-大陸地區,還比如zh-TW,即中文-臺灣地區;

!!因此可以這樣構造:Locale locale = new Locale("zh", "CN");

         iv. 當然Locale類也提供了幾個預定義的常靜態物件,比如Locale.CHINA就等價於Locale("zh", "CN")等,因此也可以這樣建立:Locale locale = new Locale(Locale.CHINA);

    4) Response設定語系來間接設定編碼:

         i. HttpServletResponse有一個方法是setLocale:void setLocale(Locale loc); // 該方法會將設定的語系寫入響應標頭的Accept-Language的值中,Accept-Language就是語系標頭

         ii. 由於每種語系都由一個預設的對映表對應著一種編碼,因此設定語系的同時也設定了相應內容的編碼,比如:resp.setLocale(Local.TAIWAN); // 呼叫該語句背後也隱含設定了相應編碼為BIG5,因為JRE預設的語系-編碼對映表中中文-臺灣對應的編碼是BIG5;

!!那麼問題來了,有時候雖然需求的語系是中文-臺灣,但是你可能需要用UTF-8來進行編碼,那麼此時就必須要修改預設的對映表嗎?其實不用,只需要在web.xml中註明一下在此Servlet中運用的對映表即可;

         iii. 在web.xml中設定語系對映編碼表需要用到的標籤:

              a. locale-encoding-mapping-list:語系-編碼對映表,裡面可以包含多個locale-encoding-mapping標籤,一個locale-encoding-mapping標籤只能定義一個語系-編碼對映對;

              b. 在locale-encoding-mapping標籤中定義對映對:locale標籤指定語系,語系的格式為"語言_地區",其中語言小寫,地區大寫,都必須是規定的兩個字母,比如zh_CN,接下來的encoding標籤中指定編碼方案,編碼方案還是按照原先的規定書寫,比如UTF-8等;

              c. 示例:

<locale-encoding-mapping-list>
	<locale-encoding-mapping>
		<locale>zh_CN</locale>
		<encoding>BIG5</encoding>
	</locale-encoding-mapping>
</locale-encoding-mapping-list>
!這樣,setLocale成CHINA後被後的字元編碼就成了BIG5了;

    5) 語系標頭、獲取請求標頭中指定的可接受語系資訊:

         i. 瀏覽器在傳送請求時可以設定Accept-Language標頭,該標頭就註明了瀏覽器可以接受的語系資訊,比如Accept-Language:zh-CN就表示接受中國大陸語系;

         ii. Servlet可以使用HttpServletRequest的getLocale從請求標頭中獲取該語系資訊並返回一個Locale物件:Locale getLocale();

         iii. 獲取該Locale物件後可以來設定響應HttpServletRespose的語系了(呼叫response.setLocale即可);

         iv. 當然也可以獲得Response物件的當前的Locale資訊:Locale HttpServletResponse.getLocale();

    6) 使用setCharacterEncoding設定響應編碼:void HttpServletResponse.setCharacterEncoding(String charset);

    7) 使用setContentType設定內容型別的同時順便設定響應編碼:void HttpServletResponse.setContentType(String type);

!!例如response.setContentType("text/html;charset=UTF-8"); // 即設定了內容型別text/html,也設定了相應編碼方案為UTF-8;

    8) 三種設定響應編碼方法的優先順序:

         i. setCharacterEncoding和setContentType都比setLocale強,如果使用了前兩者,那麼setLocale背後設定的編碼就會被忽略,因為語系只是一種表意的東西,並不能決定實際的編碼,語系的編碼可以隨便對映,語系只是一種形式上的參考,實際編碼還得看看具體是什麼;

         ii. 如果在setContentType中設定了編碼,那麼就會自動呼叫setCharacterEncoding將setContentType中設定的編碼作為引數傳入,因此setContentType的優先順序強於setCharacterEncoding;

    9) 設定響應編碼時的規範:

         i. 語系只是一種表意的方式,因此有時在標頭中寫入語系是必要的,但如果你既不想使用預設的對映也不想在web.xml中設定對映編碼,則可以用setLocale設定一下語系,然後再用setCharacterEncoding或setContentType修改其中的編碼也不失為一種好的解決方案;

         ii. 設定編碼精良使用setContenType方法,簡潔明瞭內容多!

         iii. 儘量不要同時使用setCharacterEncoding和setContentType;

!!一個例子:瀏覽器通過窗體傳送中文請求引數,Servlet響應返回中文內容:

form.html

<!DOCTYPE html>
<html>
	<head>
		<title>寵物型別大調查</title>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	</head>
	<body>
		<form action="pet" method="get">
			姓名:<input type="text" name="user" value=""><br>
			郵件:<input type="text" name="email" value=""><br>
			你喜愛的寵物代表:<br>
			<select name="type" size="6" multiple="true">
				<option value="貓">貓</option>
				<option value="狗">狗</option>
				<option value="魚">魚</option>
				<option value="鳥">鳥</option>
			</select><br>
			<input type="submit" value="送出"/>
		</form>
	</body>
</html>
doPost:
@WebServlet("/pet")
private String htmlTemplate = 
    		"<html>" +
    		" <head>" +
    		"  <title>感謝填寫</title>" +
    		" </head>" +
    		" <body>" +
    		"聯絡人:<a href='mailto:%s'>%s</a><br>" +
    		"喜愛的寵物型別" +
    		" <ul>" +
    		"  %s" +
    		" </ul>" +
    		" </body>" +
    		"</html>";
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		//doGet(request, response);
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();		
		String liType = "";
		for (String type: request.getParameterValues("type"))
			liType += "<li>" + type + "</li>";
		String html = String.format(htmlTemplate, request.getParameter("email"), request.getParameter("user"), liType);
		out.print(html);
	}
!!要把form.html放到WebRoot目錄下,而在Eclipse工程中是預設建立在WebContent中;

4. MIME簡介:

    1) MIME就是setContentType中的那個"text/html"欄位,它表示返回給客戶端內容的型別,表示文字型別的html文件,那麼MIME用來幹什麼呢?

    2) MIME全稱是:Multipurpose Internet Mail Extensions,即多用途電子郵件型別擴充套件,它表示在網路中傳輸的資源的型別;

    3) MIME的作用:

         i. 最早應用於電子郵件的附件,通常電子郵件都可以新增附件一起傳送,那麼問題來了,附件到了以後該如何開啟呢?更精確地講應該用什麼程式來開啟這個附件呢?那麼就要用MIME來指示了,如果MIME是application/pdf,那麼瀏覽器就知道應該用Adobe Reader來開啟,也就是說MIME是給瀏覽器看的,瀏覽器根據MIME來調取合適的應用程式來開啟附件;

         ii. 直到現在,MIME仍然用於表示檔案型別,只不過已經不僅僅用於標識電子郵件的附件了,現在也可以用來標識任何用HTTP協議傳輸的資源了,所以叫做Multipurpose,即多用途,並且是電子郵件的擴充套件!

    4) MIME的書寫格式:大類別/具體的種類,比如text/html,大類別就是文字檔案text,具體種類是超文字標記文件

!!注意:種類和檔案字尾是兩碼事!text/html中的html是種類而不是檔案字尾,種類指示表示檔案中的內容的型別,比如我一個以txt為字尾的文件,但是裡面的內容確實用超文字標記語言寫的,即使你檔案字尾是txt,但是瀏覽器看到你的MIME是text/html則還是會用瀏覽器而不是notepad來開啟這個檔案!

!!所以,MIME才是真正決定開啟方式的鑰匙,而檔案字尾只是一種點綴,不起到任何作用,僅僅是形式上更好的表示資源的型別,完全可以使用一個和內容型別不符的字尾來隱藏該資源的真正用途;

!!程式設計時應該做到字尾和MIME相匹配,除非你有一些不良的意圖!

    5) 常用的MIME型別:

.html:text/html

.xml:text/xml

.txt:text/plain

.pdf:application/pdf

.tar:application/x-tar

.gif:image/gif

.png:image/png

.mpg:audio/mpeg

!!可以看到都還是很有規律的