代表http請求的對象
ServletRequest – 提供一個request對象最基本的功能
|
|-- HttpServletRequest – 繼承了ServletRequest接口, 並在其基礎上添加了很多和Http協議相關的方法
getRequestURL方法 -- 返回客戶端發出請求完整URL
如: http://localhost/day09/servlet/SecondServlet
getRequestURI方法 -- 返回請求行中的資源名部分
如: /day09/servlet/SecondServlet
getQueryString方法 -- 返回請求行中的參數部分
如: username=zhangfei&password=123
getRemoteAddr方法 -- 返回發出請求的客戶機的IP地址
如: 127.0.0.1 //可能會出現0:0:0:0:0:0:0:1形式,是ipv6的表現形式。
getMethod -- 得到客戶機請求方式
如: GET或POST
!!getContextPath -- 獲得當前web應用虛擬目錄名稱
如: /day09
注意:在寫路徑時不要將web應用的虛擬路徑的名稱寫死, 應該在需要寫web應用的名稱的地方通過getContextPath方法動態獲取
getHeader(name)方法 --- String
getHeaders(String name)方法 --- Enumeration<String>
可以通過遍歷枚舉遍歷每一個信息
例如:while (values.hasMoreElements()) {
String value = (String) values.nextElement();
System.out.println(value);
}
getHeaderNames方法 --- Enumeration<String>
getIntHeader(name)方法 --- int
getDateHeader(name)方法 --- long(日期對應毫秒)
getParameter(String name) --- String 通過name獲得值
getParameterValues(String name) --- String[ ] 通過name獲得多值 checkbox
getParameterMap() --- Map<String,String[ ]> key :name value: 多值
getParameterNames() --- Enumeration<String> 獲得所有name
請求參數中的亂碼問題
亂碼分析: 編碼時和解碼時使用的碼錶不一致造成的!!
編碼: 是在瀏覽器進行的, 瀏覽器發送數據使用的是什麼編碼?
瀏覽器在打開當前頁面時使用的是什麼碼錶,也會使用相同的碼錶來發送數據.
打開頁面使用utf-8, 所以發送數據也是用utf-8碼錶
解碼: 是在服務器端進行的, 服務器端接收數據使用的又是什麼編碼?
如果不指定, 服務器會使用默認的碼錶來接收瀏覽器發送過來的數據, 默認的碼錶是iso8859-1.
解決方案:
亂碼造成的原因是編碼不一致, 所以應該讓兩端的編碼保持一致!, 應該通知服務器使用utf-8來接受客戶端發送過來的數據!!
request.setCharacterEncoding(「utf-8」);//用來通知服務器使用什麼編碼來接受請求實體內容中的數據, 如果使用的是POST提交, POST提交的請求參數就是在請求實體內容中!, 所有這個方法可以解決POST提交的亂碼問題!!!
GET提交的請求參數由於不在請求實體內容中,而是在請求行中的請求資源路徑後面拼接着, 所以這行代碼對GET提交的參數亂碼不起作用!!!
GET提交的參數亂碼問題該如何解決???
GET提交的亂碼問題可以通過手動編解碼來解決!!
//>>username爲亂碼, 通過亂碼反向編碼得回二進制數組
byte[] bytes = username.getBytes("iso8859-1");
//>>通過二進制數組查詢正確的碼錶, 得出正確的數據
username = new String(bytes, "utf-8");
請求重定向: 302狀態碼+location響應頭
請求轉發: 和請求重定向都可以實現資源的跳轉, 但是區別是請求轉發是服務器內部的並且是同一個WEB應用內部的資源跳轉
請求轉發的特點:
一次請求對應一次響應
地址欄地址不會發生變化
請求轉發只能在同一個WEB應用內部資源之間進行跳轉! 不能是不同的WEB應用或者不同的主機!
實現代碼:
創建servlet:RequestDemo3和RequestDemo4,RequestDemo3轉發到RequestDemo4
在RequestDemo3中:
/*
* 在web階段寫路徑時,除了請求轉發和請求包含在寫
* 路徑是不用包含web應用的虛擬路徑,其他地方都需要
* 加上web應用的虛擬路徑。
* 完整路徑:http://localhost/day09/servlet/RequestDemo4
*/
request.getRequestDispatcher("/servlet/RequestDemo4").forward(request, response);
在RequestDemo4中響應:
response.getWriter().write("1$")
request開發細節:
在轉發之前, 如果response緩衝區被寫入了數據但是還沒有打給瀏覽器, 在轉發時response緩衝區(數據)將會被清空!
例如:在RequestDemo3中添加代碼:
response.getWriter().write("no money!");
發現瀏覽器中並未得到"no money!"這段字符串的響應。
在轉發之前, 如果response緩衝區被寫入了數據並且已經打給了瀏覽器, 轉發將會失敗!!
例如:在RequestDemo3中添加代碼:
response.getWriter().write("no money!");
response.flushBuffer();
發現瀏覽器可以顯示"no money!",因爲強制刷新了,但是轉發就會報錯,因爲已經響應過了,一次請求對應一次響應。
在同一個Servlet中轉發不能進行多次!!(A既轉發B, 又轉發給C)
但是可以進行多重轉發(比如A轉發給B, B再轉發給C)
域對象:如果一個對象具有一個可以被看見的範圍, 利用該對象上的map可以在整個範圍內實現資源的共享!
域對象提供的方法(可以操作map中的數據):
setAttribute(String name, Object value); 用來存儲一個對象,也可以稱之爲存儲一個域屬性
getAttribute(String name); 用來獲取request中的數據
removeAttribute(String name); 用來移除request中的域屬性
getAttributeNames(); 獲取所有域屬性的名稱
生命週期:
一次請求開始時創建request對象, 一次請求結束時銷燬request對象
作用範圍:
整個請求鏈
主要功能:
在整個範圍內共享數據
帶數據到目的地
例如:在RequestDemo3中可以設置一些屬性,比如是從數據庫查詢出來的數據,轉發到RequestDemo4後,在RequestDemo4中從request域中獲取數據,並打印到頁面中。
請求包含是服務器內部資源合併的現象
如果瀏覽器訪問Servlet A, 但是A不能獨立的處理這次請求, 需要另外一個Servlet B幫忙, 於是在A中可以將B包含進來, 包含的代碼如下:
request.getRequestDispatcher(「B的路徑」).include(request, response);
將B包含進來後, 將會由A和B共同來處理這次請求, 處理的結果也會合並在一起, 一起打給瀏覽器!
Servlet中應該如何向用戶輸出數據呢?在doGet和doPost方法的參數中,HttpServletRequest代表的是http請求,而HttServletResponse代表的是http響應。想要獲取請求中的信息時使用HttpServletRequest對象,而有數據需要發送給客戶端時,就要用到HttpServletResponse對象了。
客戶端基於HTTP協議訪問服務器時,服務器創建代表請求的Request對象和代表響應的Response對象
Request對象包含了很多操作請求相關數據的方法
Response對象包含了很多操作響應數據的方法
雖然我們經常簡稱爲response,實際上是ServletResponse接口,其中定義了很多和響應對象相關的方法,HttpServletResponse是ServletResponse接口的子接口,在ServletResponse的基礎上增加了很多和http協議相關的方法。
setStatus(int sc)
setIntHeader(String name, int value)
setHeader(String name, String value)
setDateHeader(String name, long date)
PrintWriter getWriter()
ServletOutputStream getOutputStream();
查詢api,在Response向外輸出數據的方法有如下兩個:
PrintWriter getWriter()
ServletOutputStream getOutputStream();
其中getWriter獲取的是字符流,可以輸出字符數據到客戶端。
getOutputStream獲取的是字節流,可以輸出字節數據到客戶端。
我們現在要將字符數據發送給客戶端,可以調用getWriter方法向其中輸出數據
經測試可以正確的輸出。如圖:
接着我們測試中文。發現輸出時產生了亂碼。如圖:
這個亂碼是如何產生的呢?亂碼的產生大多是由於編碼和解碼時的碼錶不同產生的。
那麼服務器是以什麼碼錶來發送數據呢?我們發現亂碼是以「?」的形式出現的。根據我們的經驗,這種問題多半是由ISO8859-1編碼導致的。
確實是的,如果不指定,服務器默認將用iso8859-1進行編碼發送數據。瀏覽器用什麼碼錶打開呢?一般來說如果不指定,瀏覽器默認會用所在的操作系統的平臺碼,我們當前的中文系統中,默認就是使用GB2312作爲解碼碼錶的。
首先iso8859-1中沒有中文,對於無法表示的字符,iso8859-1會用「?」來替代,所以真正發送給瀏覽器的數據其實是「?」,世界上所有的碼錶都默認兼容iso8859-1,所以gb2312認識,顯示爲了「?」。如圖:
在解決這個問題時,可以通過設置response.setCharacterEncoding(「gbk」)來指定服務器發送數據時使用的碼錶。同時要注意,此行代碼必須出現在任何輸出數據的代碼之前,如果在這行代碼之前已經有任何數據寫入給了response,則此行代碼無效。
設置過後再重新測試。發現仍然是亂碼,但不再是「??」而是變成了「涓浗」。如圖:
這種類型的亂碼是怎麼發生的呢?我們接着分析。服務器用utf-8發送數據給瀏覽器,而瀏覽器用平臺碼(當前爲gbk)gbk打開自然產生了亂碼。如圖:
這種亂碼的產生是由於瀏覽器沒有使用正確的編碼打開造成的,那麼我們該如何控制瀏覽器用指定碼錶打開數據呢?
在http協議中有一個響應頭叫做Content-Type可以用來通知瀏覽器當前服務器發送的數據的格式,如果是字符格式的數據還可以指定解析時使用的碼錶。所以我們可以通過如下方法通知瀏覽器用指定碼錶打開發送的數據,代碼如下,經測試沒有亂碼。
我們通過response.setHeader("Content-Type", "text/html;charset=utf-8");通知服務器發送數據時的碼錶。
通過response.setCharacterEncoding("utf-8");通知瀏覽器解析時使用的碼錶。
兩碼相同就不會有亂碼了。
如圖:
另外response提供了setContentType()快捷方法,在它的底層,會同時做上面兩件事,所以可以一行代碼解決response產生的亂碼問題。如圖:
(1)getOutputStream和getWriter這兩個方法互相排斥,調用了其中的任何一個方法後,就不能再調用另一方法。
(2)Servlet程序向ServletOutputStream或PrintWriter對象中寫入的數據將被Servlet引擎從response裏面獲取,Servlet引擎將這些數據當作響應消息的正文,然後再與響應狀態行和各響應頭組合後輸出到客戶端。
(3)Serlvet的service方法結束後,Servlet引擎將檢查getWriter或getOutputStream方法返回的輸出流對象是否已經調用過close方法,如果沒有,Servlet引擎tomcat將調用close方法關閉該輸出流對象。
在HTTP協議中提供了302狀態碼和Location響應頭,通知瀏覽器收到響應後立即自動訪問Location中指定的地址,從而跳轉訪問另一個資源。
response.setState(302);
response.setHeader(「Location」,「。。。」);
經測試可以實現跳轉效果。
同時我們打開HTTPWatch,發現在整個重定向的過程中發生了兩次請求響應。
在HTTP協議中,提供了refresh響應頭,可以命令瀏覽器經過多少秒刷新頁面到哪個地址。
我們經常見到這樣的效果,當註冊成功後提示「恭喜您註冊成功。。3秒後回到主頁。。」,緊接着經過3秒回到主頁。這就是定時刷新的效果。
通過如下代碼實現。
瀏覽器爲了減少對服務器的訪問,會在第一次訪問到資源後進行緩存,之後再訪問,就直接用緩存中的數據,減少對服務器的訪問。
這種緩存機制,提高了瀏覽器的響應數度,減少了服務器的訪問量,在大多數情況下是好的,但是在某些場景下可能有問題。
比如:驗證碼 實時的數據 – 火車票餘量 股票價格。。。
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
===========================================================================
資源的三種條轉方式:
請求轉發:
在服務器內發生的資源跳轉
一次請求一次響應,一個request對象,可以用request域在轉發時傳遞數據
瀏覽器不知道請求轉發的存在 地址欄仍然是原來的地址 刷新操作時仍然訪問的是第一個資源的地址
地址是內部使用地址 不需要寫應用名稱
request.getRequestDispatcher("/index.jsp").forward(request,response);
請求重定向:
在服務器外發生的資源跳轉
兩次請求兩次響應,兩個request對象,不可以用request域在重定向時傳遞數據
瀏覽器知道請求重定向的存在 地址欄發生變化 刷新操作時訪問的是最後一個資源地址
地址是外部使用地址 一定要寫應用名稱
response.sendRedirect("/Day10/index.jsp")
定時刷新:
在服務器外發生的資源跳轉
兩次請求兩次響應,兩個request對象,不可以用request域在重定向時傳遞數據
瀏覽器知道定時刷新的存在 地址欄發生變化 刷新操作時訪問的是最後一個資源地址
地址是外部使用地址 一定要寫應用名稱
response.setHeader("refresh","3;url=/Day10/index.jsp")
三種跳轉方式的選擇:
如果需要利用request域傳遞數據 --> 必須請求轉發
如果想要在資源跳轉時改變地址欄 或 刷新操作 --> 請求重定向 或 定時刷新
如果想在資源跳轉時,向用戶提示一些信息 --> 定時刷新
如果以上三種需求都沒有,三種方案都可以時,優先使用 請求轉發,因爲請求轉發訪問服務器的次數少,減輕服務器壓力。
============================================================================
每個Servlet都有一個ServletConfig對象 代表當前Servlet在web.xml中的配置信息
Servlet在初始化時候 init方法中傳入了ServletConfig對象
我們通常寫的Servlet繼承自HttpServlet - GenericServlet - Servlet
在GenericServlet中 實現了 Servlet接口定義的init方法 在其中將ServletConfig對象保存到了類的內部 比提供了 getServletConfig方法 返回該對象
所以作爲GenericServlet的子孫類 可以直接調用getServletConfig方法來獲取次對象
Enumeration getInitParameterNames() 獲取當前Servlet所有初始化參數的名字的枚舉
代表當前web應用
當服務器啓動時,服務器在啓動時會依次加載web應用,每一個web應用加載完成後都會創建一個ServletContext對象唯一代表該web應用,這個對象一直存活,直到web應用移除出容器或服務器關閉時,隨着應用銷燬,ServletContext對象跟着銷燬。
ServletContext一個應用只有一個,唯一的代表當前web應用,生命週期和web應用一樣,web應用創建它就創建 web應用銷燬它就跟着被銷燬。
方法1:
ServletConfig.getServletContext();
方法2:
this.getServletContext();
可以在web.xml的根目錄下 通過<context-param>來爲整個web應用配置初始化參數
這些初始化參數可以通過ServletContext對象來獲取
String getInitParameter(String name) 獲取當前web應用指定名稱的初始化參數的值
Enumeration getInitParameterNames() 獲取當前web應用所有初始化參數的名字的枚舉