一個完整的http響應包括響應行,若干響應頭和響應數據主體三部分構成。如果我們能用響應對象來進行這三部分的處理,就能向客戶發送特定的響應數據包。
先從httpservletresponse對象的方法中可以看到有如下方法(部分):
這只是一部分,但是我們卻可以看出,通過響應對象的方法,我們就能設置響應客戶端數據的一些信息。比如setstatus(int sc)方法,我們從httpservletresponse的api中的字段定義可找到已經設置好的響應碼(部分):
我們通過setheader或者addheader就能對一些數據進行跟客戶端的告知,比如我想讓某個頁面的數據在客戶端保存一天,也就是如果客戶端再向我請求的話,則它應該去緩存中獲取,直到一天之后才能重新向我請求,那么我就必須使用到了“expires”響應頭,將這個響應頭的值設為一天后的時間告訴給客戶端:
在myeclipse中的【myservlet】web工程下,創建名為servletdemo1的servlet,代碼如下:
1
2
3
4
5
6
7
8
9
|
public class servletdemo extends httpservlet { public void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { long expirestime = system.currenttimemillis()+ 1 * 24 * 60 * 60 * 1000 ; //將緩存截止時間設置為一天后 response.setdateheader( "expires" , expirestime); } } |
這時候用瀏覽器來訪問這個servlet(訪問之前最好現將瀏覽器中的緩沖清除干凈),訪問之后我們再來看看這個緩存的頁面文件:
右鍵查看其屬性:
可以看到我們是在9月28日訪問的這個文件,而服務器已經將這個文件的緩沖時間設置為了29日。
注意:對于”expires”響應頭的設置必須使用setdateheader方法,使用setheader方法無效。
使用響應對象可以向客戶端寫入數據,我們采用獲取響應對象的輸出流,將數據用write方法寫,這些數據是寫入到響應對象中,而服務器再將這些響應對象回傳給客戶端進行解析。而這些數據在響應對象中正是處于http協議的響應數據實體中。
通過httpservletresponse對象的父類servletresponse對象的getoutputstream方法和getwriter方法可以獲取向響應對象的數據實體中寫入數據。這里如果傳送的是英文數據一般都沒有什么問題,而中文編碼則是會令人頭疼的。
首先我們來看響應對象的getoutputstream方法,我們在一個servlet程序中代碼如下:
1
2
3
|
string data = "銀魂" ; outputstream out = response.getoutputstream(); out.write(data.getbytes()); |
而在瀏覽器中訪問這個servlet,看到的是:
是的,沒有出現亂碼的問題,這主要有兩點原因:
一個是瀏覽器本身的解碼方式是根據平臺語言環境來設置的,我的操作系統是windows的中文版本,因此瀏覽器的解碼采用的編碼表為“gb2312”:
另一個原因是因為,在將字符串轉為字節數組時,采用了getbytes()方法,這個方法在string類的api文檔中明確說明了采用平臺默認的字符集,根據我的系統這個方法也采用了“gb2312”編碼表。
因此服務器編碼和客戶端解碼都采用同一編碼表這就不會出現中文亂碼問題。
如果我在getbytes方法中采用utf-8編碼,那么結果自然會出錯:
1
2
3
|
string data = "銀魂" ; outputstream out = response.getoutputstream(); out.write(data.getbytes( "utf-8" )); |
除非你也在瀏覽器中更改編碼方式,改成utf-8就可以重新看到正確的中文數據了:
當然這肯定不適合給用戶這么操作,畢竟不是誰都懂瀏覽器的編碼。
如果我們一定要將中文數據采用“utf-8”的方式(utf-8有利于國際化),有這么兩種解決采用utf-8編碼方式的中文亂碼問題:
第一種解決方式:使用httpservletresponse響應對象的setheader的方法,將“content-type”這個響應頭中設置編碼方式。同時,sun公司也提供了更便捷的代碼語句setcontenttype給編程人員使用。
在servlet中的代碼:
1
2
3
4
5
|
response.setheader( "content-type" , "text/html;charset=utf-8" ); //response.setcontenttype("text/html;charset=utf-8"); //這句功能同上一句 string data = "銀魂" ; outputstream out = response.getoutputstream(); out.write(data.getbytes( "utf-8" )); |
這樣在瀏覽器中可以看到正確的中文數據,并且瀏覽器自動將編碼方式采用utf-8:
附帶從httpwatch中觀察到的數據包:
注意:如果response.setheader("content-type", "text/html;charset=utf-8");中將"text/html;charset=utf-8"中的分號“;”寫成了逗號“,”就會變成下載該servlet文件。
所以書寫要注意。
第二種解決方式:我們不直接在響應對象中設置“content-type”這個響應頭,而是通過html的<meta>標簽,該標簽的作用就是模擬一個響應頭,這樣在回傳的響應對象中,某些響應頭就不會被設置,但是還是有這個響應頭的功能,例如我們在html頁面中經常能見到的<meta http-equiv="content-type" content="text/html;charset=utf-8">這個標簽,是不是和第一種方式很像。相關代碼為:
1
2
3
4
|
string data = "銀魂" ; outputstream out = response.getoutputstream(); out.write( "<meta http-equiv='content-type' content='text/html;charset=utf-8'>" .getbytes()); out.write(data.getbytes( "utf-8" )); |
這時候在瀏覽器中同樣能觀察到正確的中文數據,同時可以看到瀏覽器已經自動采用“utf-8”編碼方式:
同時,在瀏覽器瀏覽源代碼和觀察httpwatch窗口:
從上面可以看出,服務器發回的響應中沒有“content-type”這個響應頭,但是在響應數據實體中有<meta>標簽,瀏覽器能解析這個html語言,得到這個標簽中設置的“content-type”模擬響應頭,因此能根據這個模擬響應頭中的編碼方式來設置瀏覽器應該采用的碼表。
如果我們用輸出流直接輸出數字的話,會是輸出這個數字在編碼表中代表的字符,如代碼為:
1
2
3
4
5
|
public void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { response.getoutputstream().write( 97 ); } |
在瀏覽器中得到:a。
說完了從響應對象中得到outputsteam對象,接著我們來討論從響應對象中得到writer對象,眾所周知,字符流是由字節流加編碼表組成,那么在響應對象中的字符流采用什么編碼表方式呢?我們來看看httpservletresponse對象的getwriter()方法的api手冊說明:
從這里面看出如果沒有為這個getwriter()方法設置編碼表,那么則默認采用 “iso-8859-1”編碼表。或者采用響應對象的getcharacterencoding()方法查看也可以。
那么在服務器端如果要改變對封裝數據的編碼格式可以有兩種方式:
第一種:使用響應對象的setcharacterencoding()方法來設置服務器采用的編碼表,接著使用setcontendtype或者setheader告知客戶端服務器采用的編碼表,后者在上面已經說過。
示例代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class servletresponse extends httpservlet { public void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { response.setcharacterencoding( "utf-8" ); response.setcontenttype( "text/html;charset=utf-8" ); printwriter writer = response.getwriter(); string data = "銀魂" ; writer.write(data); } } |
注意,光只有setcharacterencoding()方法只能改變服務器端采用的編碼表,而沒能通知客戶端,所以需要setcontenttype設置“content-type”響應頭,或者如之前所說的寫入<meta>標簽來模擬“content-type”響應頭。
第二種:直接使用setcontenttype方法,通過這種方法,可以在服務器和客戶端同時設置編碼表,也就是第一種方式中兩個方法的結合,因此上述示例的代碼如下:
1
2
3
4
5
6
7
8
9
|
public void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { response.setcontenttype( "text/html;charset=utf-8" ); printwriter writer = response.getwriter(); string data = "銀魂" ; writer.write(data); } } |
以上分別從響應對象httpservletresponse中根據獲取的字節流或者字符流向客戶端輸出數據時會碰到的中文亂碼問題做出了分析和解決。