JavaWeb學習篇之----容器Request詳解
前篇說到了Response容器物件,這篇我們就來看一下Request容器物件,之前也說過了,這個兩個容器物件是相對應的,每次使用者請求伺服器的時候web容器就會給建立這對容器物件,他們是共存亡的,當然Request除了有一個容器物件的角色,他還有一個角色就是Request域,我們之前在講解Servlet的時候,說到一個ServletContext域,這個域的範圍是整個web應用,這裡的Request域的範圍就小了,他只是一次使用者的請求內,即使用者傳送一個請求的時候,Request建立,當用戶關閉這次請求的時候Request就會消亡的。
下面就來看一下Request的相關方法:
getContextPath()
getCookies():這個方法返回的是一個Cookies[],我們在response容器那篇文章中看一個方法是向response容器中存入一個cookie的,這個方法是從Request容器中拿取多個cookies,因為使用者在請求的時候會攜帶很多的cookie,關於Cookie的相關知識,我們會在後面的文章中進行詳解
getHeader(String name)/getIntHeader(String name)/getDateHeader(String name):這些方法是獲取請求頭資訊的,只是針對不同的型別的,有字串型別的,時間型別,數值型別的
getHeaderNames():這個方法是獲取所有請求頭的欄位名稱
getHeaders(String name):這個方法是獲取一個請求頭欄位的所有值,因為有時候可能會有相同請求頭欄位資訊,不會覆蓋的
getMethod():這個方法是獲取客戶機的請求方法
getQueryString():這個方法是獲取使用者請求時的查詢引數的,即url後面攜帶的引數,如:http://localhost:8080/ServletDemo/ServletRequest?username=jiangwei&password=123456,那麼getQueryString()方法返回來的值就是username=jiangwei&password=123456
getRequestSessionId():這個方法是獲取客戶機在請求的時候攜帶的sessionid值,有關session的相關知識,後面會詳解
getRequestURL():這個方法是獲取客戶機請求的url
getServletPath():這個方法返回的是請求的Servlet的對映路徑,比如:ServletRequest對映的是是/ServletRequest
getServerName()/getServerPort():這兩個方法是獲取伺服器的名稱和埠號,比如localhost,8080
getSession()/getSession(boolean mode):這兩個方法是獲取一個session物件,相關之後在session篇會說到
getAttribute(String name):這個方法是從Request域中獲取值
getAttributeNames():這個方法是獲取Request域中獲取所有的欄位名稱
getParameter(String name):這個方法是獲取使用者使用get/post方式攜帶的引數值
getParameterNames():這個方法是獲取使用者請求時攜帶的所有引數名稱
getParameterMap():這個方法是獲取使用者請求時攜帶的引數名稱和引數值,並將其組裝成一個Map物件
getParameterValues():這個方法是獲取使用者請求攜帶的引數值,因為有時候一個引數名稱可能對應多個值
setAttribute(String name,Object value):這個方法是設定Request域中的屬性值
removeAttribute(String name):這個方法是刪除Request域中的屬性值
getInputStream()/getReader():這個方法是獲取使用者請求的時候上傳的輸入流,比如我們在處理使用者上傳檔案的時候。需要用到這個輸入流
setCharacterEncoding(String name):這個方法是設定Request容器的編碼
getRemoteAddr()/getRemoteHost():獲取客戶機的IP地址和主機名
getProctocol():獲取協議名稱
getRequestDispatcher(String path):獲取一個轉發物件RequestDispatcher,進行轉發操作
下面就通過例項來看一下上面方法的使用:
public void test1(HttpServletRequest request) throws Exception{
//有時候可能有多個name
String[] nameAry = request.getParameterValues("username");
//在獲取使用者的請求資料的時候先要進行判斷資料的有效性,然後再去使用,提高應用的健壯性
if(nameAry != null){
System.out.println("getParameterValues方法");
System.out.println("---------------------");
System.out.println("引數名稱:username");
for(int i=0;i<nameAry.length;i++){
System.out.println(nameAry[i]+",");
}
System.out.println();
}
System.out.println("getParameterMap方法");
System.out.println("------------------");
Map<String,String[]> map = request.getParameterMap();
if(map != null){
Set<Entry<String,String[]>> set = map.entrySet();
Iterator<Entry<String,String[]>> iterator = set.iterator();
while(iterator.hasNext()){
Entry<String,String[]> entry = iterator.next();
System.out.println("引數名:"+entry.getKey());
String[] values = entry.getValue();
if(values != null){
for(int i=0;i<values.length;i++){
System.out.print(values[i]+",");
}
System.out.println();
}
}
}
System.out.println();
System.out.println("getParameterNames()方法");
System.out.println("----------------------");
Enumeration names = request.getParameterNames();
if(names != null){
while(names.hasMoreElements()){
String name = (String) names.nextElement();
System.out.println("引數名:"+name);
System.out.println("引數值:"+request.getParameter(name));
}
}
}
這裡我還需要設計一個demo.jsp,在裡面設計一個表單進行資料的上傳:
<form action="/ServletDemo/ServletRequest" method="post">
使用者名稱1:<input type="text" name="username"/><br/>
使用者名稱2:<input type="text" name="username"/><br/>
密碼:<input type="text" name="password"/><br/>
<input type="submit" value="提交"/>
</form>
我們傳遞了兩個引數名稱為:username的欄位,我們在瀏覽器中輸入:http://localhost:8080/ServletDemo/demo.jsp,然後列印結果:
getParameterValues方法
---------------------
引數名稱:username
aaa,
bbb,
getParameterMap方法
------------------
引數名:username
aaa,bbb,
引數名:password
123,
getParameterNames()方法
----------------------
引數名:username
引數值:aaa
引數名:password
引數值:123
下面我們再來看一下request的亂碼問題:
我們還是直接使用demo.jsp中的方式傳遞引數,當我們在頁面文字框中輸入"中國"
在控制檯中列印獲取到的username的值,顯示的是??,這個是因為Request域中的採用的是ISO8859-1碼錶的,而我們的demo.jsp使用的是utf-8編碼的,所以當我們點選提交的時候,瀏覽器會將"中國"使用utf-8碼錶編碼,然後web容器建立一對request/response容器物件,資料傳入到request容器中,因為request容器使用的是iso8859-1編碼的,所以當我們在Servlet中從request容器中讀取資料,使用的是iso8859-1進行解碼的,所以會出現亂碼了,所以我們只需要將request的容器碼錶設定成和我們頁面顯示的碼錶一樣就可以了。這樣我們在getParameter的時候得到正確的解碼(utf-8)資料
request.setCharacterEncoding("utf-8");
String name = request.getParameter("username");
System.out.println("username:"+name);
這時候就可以了,就能夠正常顯示了。
但是問題還沒有結束,以上說到的亂碼問題是在使用post方式傳遞的資料,下面我們在來看一下使用get方式傳遞資料的亂碼問題,
昨天突然發現request.setCharacterEncoding("UTF-8")這句程式碼失效,前後臺編碼統一都是UTF-8,但通過request.getParameter("name")接收到的表單資料依然亂碼,後來發現原因是表單的提交方式沒有設定,也就是採用了預設的GET方式提交。那為什麼GET方式會出現問題?難道request.setCharacterEncoding("UTF-8")這句程式碼只對POST方式提交資料才有效?
做了一些測試之後總結出了一點規律:
1、web瀏覽器對頁面上通過GET方式提交的資料會進行URL編碼,採用的編碼方式通常由html頁面上
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
這個標籤內的charset所指定的編碼方式決定(前提是:沒有自定義瀏覽器傳送資料的編碼設定)。比如現在charset="UTF-8",那麼當採用get方式提交表單的時候,web瀏覽器預設會採用UTF-8的編碼方式對資料進行編碼。
所以,當請求的URL如下時:
http://localhost:8080/test/TestServlet?name=中國
其實真實內容是這樣:
http://localhost:8080/test/TestServlet?name=%E4%B8%AD%E5%9B%BD
其中%E4%B8%AD%E5%9B%BD是對‘中國’二字採用了UTF-8的URL編碼之後產生的字串。
2、所以,當資料被髮送到Web伺服器上的時候(測試使用tomcat6),伺服器要做的一件事就是解碼%E4%B8%AD%E5%9B%BD這個字串。那麼如何解碼這個字串?
3、在JDK的java.net包下面有一個類叫做URLDecoder,該類即可對URL編碼之後的字串進行解碼,如:
URLDecoder.decode("%E4%B8%AD%E5%9B%BD","UTF-8");
返回解碼之後的字串,第二個引數是指採用何種字符集解碼"%E4%B8%AD%E5%9B%BD"這個字串。列印以上程式碼成功顯示“中國”二字,說明解碼正確! 4、前面已經說過了:web伺服器會自行解碼%E4%B8%AD%E5%9B%BD這個字串,但是我們通過request.getParameter("name")得到的卻是亂碼,所以問題一定出在web伺服器在解碼E4%B8%AD%E5%9B%BD這個字串的時候採用的字符集不對。
5、經過測試發現,web伺服器(只測試了tomcat6)對GET方式的資料提交採用的解碼字符集是"ISO-8859-1",所以web伺服器其實是這樣解碼的:
URLDecoder.decode("%E4%B8%AD%E5%9B%BD","ISO-8859-1");
因此:明顯伺服器的解碼方式是不對的,因為編碼採用的是UTF-8,而解碼卻用的ISO-8859-1。
6、所以,request.getParameter("name")返回的是用ISO-8859-1解碼的字串,那麼必然是亂碼了!
那麼如何獲得正確編碼的字串?可以採用以下的方式:
String a = new String(request.getParameter("name").getBytes("ISO-8859-1"),"UTF-8");
總結:因為GET方式提交資料會被瀏覽器進行URL編碼,而tomcat伺服器會採用了錯誤的解碼方式進行解碼,所以得的是亂碼。而POST方式提交的資料不會被瀏覽器進行URL編碼,所以伺服器直接按照request.setCharacterEncoding("UTF-8") 所指定的編碼方式解析字串,因此在POST方式下request.setCharacterEncoding("UTF-8")是好用的!
這裡的解決方式有兩種,
一種:是通過String類的getBytes方法進行編碼轉換,具體java程式碼是:
new String(request.getParameter(“name”).getBytes(“iso-8859-1”),“客戶端編碼方式”)
這裡的客戶端編碼方式就是頁面的編碼,比如demo.jsp中使用的是utf-8
第二種:在伺服器server.xml程式碼中改配置資訊:
<Connector port="8080"protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000"
redirectPort="8443"URIEncoding="客戶端編碼"/>
這樣我們就修改了Tomcat中的編碼和解碼字元集了。當tomcat獲取客戶機使用get方式帶來的資料使用URL解碼的字符集
當然我們之後修改server.xml之後需要重啟伺服器的,所以第二種方式是不贊同使用的。
上面我們就講述瞭如何解決request的亂碼問題
下面我們再來看一下請求轉發的問題:
對於重定向和轉發我們這裡就不做太多的介紹了,之前不知道說了好多遍了,我們之前說過ServeltContext也是可以得到一個轉發物件RequestDispatch的,其實Request也是可以得到一個RequestDispatch物件的,我們還是來做個例子,通過一個servlet轉發到另一個servlet:
Servlet1程式碼:
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//在request域中存入一個屬性值,然後轉發到Servlet2中進行讀取
request.setAttribute("data", "Hello Servlet2");
request.getRequestDispatcher("/Servlet2").forward(request, response);
}
我們在request域中存入data屬性,然後在Servlet2中讀取出來進行顯示:
Servlet2程式碼:
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//獲取屬性值
String data = (String) request.getAttribute("data");
//列印
response.getWriter().write(data);
}
執行結果:
我們取出來了屬性data的值,顯示成功了,但是我們記得在之前說的ServletContext域中存入了data屬性值,那個也是可以讀取的,但是那時候我們說過那種方式是不可靠的,因為ServletContext是全域性的,整個web應用都是有效的,所以可能發生資料錯亂的情況,比如現在一個人去請求servlet1,就在要進行轉發到servlet2的時候,這時候又來一個人去請求servlet1,這時候他也去設定data屬性值,因為ServletContext是全域性的,所以第一個人在servlet2中讀取的可能是第一個人在servlet1中存入的屬性值,那樣資料就亂了,所以我們只能使用request域了,而且轉發都是在一個request域中的,當多個使用者來訪問servlet1的時候,是有多個request域的,所以互相是不會干擾的,這樣資料也是不會亂的,所以說在使用轉發技術的時候使用request域存資料是可靠的,而不是用ServletContext域。
下面在來看一下使用轉發技術的時候我們需要注意的問題:
當我們在使用轉發的時候不能將response流關閉了,不然會報錯的,即在request.getRequestDispatcher("index.jsp").forward(request,response);這行程式碼前不能將response.getOutputStream流關閉。
//關閉流
response.getOutputStream().close();
//進行轉發
RequestDispatcher rd = request.getRequestDispatcher("/demo.jsp");//不是使用ServletContext的
rd.forward(request, response);
異常:
這個關閉流操作是很明顯的,下面我們在看一下一個不明顯的,也是最容易犯錯的:
request.getRequestDispatcher("/demo.jsp").forward(request, response);
//轉發完之後再去轉發
request.getRequestDispatcher("/index.jsp").forward(request, response);
從程式碼中我們可以看到是在一次轉發之後,在通過一次轉發,這個報錯的原因和上面的是一樣的,因為當一個轉發之後,response就是想瀏覽器輸出資料了,當輸出完資料之後,response就會自動的關閉流,所以會報和上面的錯誤,這種錯誤是經常犯的,因為我們有時候會在不同的頁面或者servlet中進行轉發,這樣就可能造成這種錯了,所以我們解決這種問題就是:
在每次呼叫轉發程式碼之後一定要記得新增一句程式碼:return;
這樣後續的程式碼就不會執行了,所以就不會有這種錯誤了,同樣的前面說到的重定向也是這樣的問題,為了我們在重定向之後,後續的程式碼不在執行,所以必須新增一句程式碼:return;
關於轉發還有一個問題就是,在使用轉發技術的時候,會沖掉response中已寫入的資料:例項程式碼如下:
//在呼叫轉發之前向response中寫入的資料,會被沖掉
String data = "aaaaaa";
response.getWriter().write(data);
request.getRequestDispatcher("/demo.jsp").forward(request, response);
這樣我們只能看到demo.jsp頁面,而看不到"aaaaaa"資料了,因為他被沖掉了。
最後我們在看一下在web應用中怎麼書寫各種路徑:
規則:
寫任何地址都是使用斜槓開頭:如果是寫給伺服器用的這個"/"就代表當前web應用,如果是寫給瀏覽器用的:這個"/"是指當前網站
瀏覽器用的:客戶機需要這個地址去請求伺服器
伺服器用的:伺服器本身用的
例項:
//1:寫給伺服器用的
request.getRequestDispatcher("/form1.html").forward(request, response);
//2:寫給瀏覽器的
response.sendRedirect("/ServletDemo/form1.html");
//3:寫給伺服器用的
this.getServletContext().getRealPath("/WEB-INF/form1.html");
//4:寫給伺服器用的
this.getServletContext().getResourceAsStream("/form1.html");
//5:寫給瀏覽器用的
/**
* <a href="/ServletDemo/form1.html">點點</a>
*/
//6:寫給瀏覽器用的
/**
* <form action="/ServletDemo/form1.html">
* </form>
*/