1. 程式人生 > >JavaWeb學習篇之----容器Request詳解

JavaWeb學習篇之----容器Request詳解

前篇說到了Response容器物件,這篇我們就來看一下Request容器物件,之前也說過了,這個兩個容器物件是相對應的,每次使用者請求伺服器的時候web容器就會給建立這對容器物件,他們是共存亡的,當然Request除了有一個容器物件的角色,他還有一個角色就是Request域,我們之前在講解Servlet的時候,說到一個ServletContext域,這個域的範圍是整個web應用,這裡的Request域的範圍就小了,他只是一次使用者的請求內,即使用者傳送一個請求的時候,Request建立,當用戶關閉這次請求的時候Request就會消亡的。

下面就來看一下Request的相關方法:

getContextPath()

:這個方法返回的是web應用對映的虛擬目錄地址:如ServletDemo應用的虛擬目錄是:/ServletDemo

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>
		 */